react-helmet
react-helmet copied to clipboard
JSX Fragments
React introduced JSX fragments in version 16.2.0.
When attempting to use a fragment inside a <Helmet>
component I get this error:
You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.
Seems like there is a check for valid tags and fragments are not supported yet: https://github.com/nfl/react-helmet/blob/master/src/HelmetConstants.js#L7 https://github.com/nfl/react-helmet/blob/master/src/Helmet.js#L185
Is JSX Fragment support a feature already being considered for development on React Helmet, or would outside contributions be welcomed to add this feature (as per the contribution guidelines)?
+1 here just ran into this when doing something like
export default function PageMeta({
$state: {
selected: { meta },
},
...props
}: PageMetaProps) {
return (
<Helmet>
<meta charSet="utf-8" />
<title>
{meta.title}
</title>
<OpenGraph {...props} />
</Helmet>
);
}
type OpenGraphProps = {
title?: string,
description?: string,
image?: string,
url?: string,
type?: string,
cardType?: 'summary' | 'summary_large_image' | 'app' | 'player',
creator: string,
};
function OpenGraph({
title, description, image, url, type, cardType, creator,
}: OpenGraphProps) {
return (
<React.Fragment>
{title && <meta property="og:title" content={title} />}
{description && <meta property="og:description" content={description} />}
{image && <meta property="og:image" content={image} />}
{url && <meta property="og:url" content={url} />}
{type && <meta property="og:type" content={type} />}
{cardType && <meta name="twitter:card" content={cardType || 'summary_large_image'} />}
<meta name="twitter:creator" content={creator} />
</React.Fragment>
);
}
I've investigated this issue a bit with the intent of putting in a PR, and it's not as simple as just editing HelmetConstants.js
:
- React version needs to be upgraded to 16
- PhantomJS needs to be polyfilled for ES6
Set
as a result - Karma throws because it's trying to stringify a symbol
-
Fragment
children can be either an object or an array, depending on whethern > 1
, and then the children themselves need to be flattened into the broader array of children (i.e., Fragment elements need to be effectively handled like arrays).
I'd say I'm about 50% of the way to a pull request but want some indication from the maintainers that it'll be accepted/I'm on the right track before I invest the effort. I think it's a worthwhile addition to the library, both because Fragments are part of standard React and also because it enables conditional patterns like the one @bradennapier discusses above.
Currently you can use a function to convert from Fragments to array of keyed elements. Something like this:
renderFragments() {
const fragments = [this.renderVerificationMeta(), this.renderIcons()];
return fragments.reduce(
(acc, { props: { children } }, index) =>
acc.concat(
React.Children.map(children, (child, childIndex) =>
React.cloneElement(child, {
// eslint-disable-next-line react/no-array-index-key
key: `${index}-${childIndex}`,
}),
),
),
[],
);
}
renderVerificationMeta() {
return (
<>
<meta
name="google-site-verification"
content="..."
/>
</>
);
}
This will at least free you from typing key
manually
Is there any update on this? React 16.2.0 came out just about a year ago, and Helmet is still on 15.x.
I just ran into this issue myself, and it took me several hours to determine the issue, thanks to cryptic error messages. The following are the errors thrown (in the listed numbers) for a single instance of Fragment
. None of these stack traces goes back to my own code, and none of them suggests what the underlying issue is.
Error 1 (x8)
Helmet.js:133 Uncaught TypeError: Cannot convert a Symbol value to a string
at ProxyComponent.warnOnInvalidChildren (Helmet.js:133)
at ProxyComponent.warnOnInvalidChildren (react-hot-loader.development.js:648)
at Helmet.js:162
at forEachSingleChild (react.development.js:1139)
at traverseAllChildrenImpl (react.development.js:1043)
at traverseAllChildrenImpl (react.development.js:1059)
at traverseAllChildren (react.development.js:1114)
at Object.forEachChildren [as forEach] (react.development.js:1159)
at ProxyComponent.mapChildrenToProps (Helmet.js:151)
at ProxyComponent.mapChildrenToProps (react-hot-loader.development.js:648)
at ProxyComponent.render (Helmet.js:201)
at finishClassComponent (react-dom.development.js:14301)
at updateClassComponent (react-dom.development.js:14264)
at beginWork (react-dom.development.js:15082)
at performUnitOfWork (react-dom.development.js:17820)
at workLoop (react-dom.development.js:17860)
at HTMLUnknownElement.callCallback (react-dom.development.js:149)
at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
at invokeGuardedCallback (react-dom.development.js:256)
at replayUnitOfWork (react-dom.development.js:17107)
at renderRoot (react-dom.development.js:17979)
at performWorkOnRoot (react-dom.development.js:18837)
at performWork (react-dom.development.js:18749)
at performSyncWork (react-dom.development.js:18723)
at requestWork (react-dom.development.js:18592)
at scheduleWork (react-dom.development.js:18401)
at scheduleRootUpdate (react-dom.development.js:19069)
at updateContainerAtExpirationTime (react-dom.development.js:19097)
at updateContainer (react-dom.development.js:19154)
at ReactRoot../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render (react-dom.development.js:19416)
at react-dom.development.js:19556
at unbatchedUpdates (react-dom.development.js:18952)
at legacyRenderSubtreeIntoContainer (react-dom.development.js:19552)
at render (react-dom.development.js:19613)
at app.js:62
Error 2 (x3)
index.js:2177 The above error occurred in the <HelmetWrapper> component:
in HelmetWrapper (at post.tsx:219)
in div (created by IndexLayout)
in IndexLayout (created by PageTemplate)
in PageTemplate (created by PageRenderer)
in PageRenderer (created by JSONStore)
in JSONStore (created by EnsureResources)
in ScrollContext (created by EnsureResources)
in RouteUpdates (created by EnsureResources)
in EnsureResources (created by RouteHandler)
in RouteHandler (created by Root)
in div (created by FocusHandlerImpl)
in FocusHandlerImpl (created by Context.Consumer)
in FocusHandler (created by RouterImpl)
in RouterImpl (created by LocationProvider)
in LocationProvider (created by Context.Consumer)
in Location (created by Context.Consumer)
in Router (created by Root)
in Root
in _default (created by HotExported_default)
in AppContainer (created by HotExported_default)
in HotExported_default
React will try to recreate this component tree from scratch using the error boundary you provided, LocationProvider.
Error 3 (x2)
index.js:2177 The above error occurred in the <LocationProvider> component:
in LocationProvider (created by Context.Consumer)
in Location (created by Context.Consumer)
in Router (created by Root)
in Root
in _default (created by HotExported_default)
in AppContainer (created by HotExported_default)
in HotExported_default
React will try to recreate this component tree from scratch using the error boundary you provided, AppContainer.
I also stumbled into this problem. Are there any plans of allowing fragments as children?
I've got the same problem. It would really be great to get this working.
Any updates on this?
Hitting the same issue here.
A workaround is to have different <Helmet>
's:
{title && (
<Helmet>
<title>{title}</title>
</Helmet>
)}
{description && (
<Helmet>
<meta name="description" content={description} />
<meta itemProp="description" content={description} />
<meta name="twitter:description" content={description} />
<meta property="og:description" content={description} />
</Helmet>
)}
👍 ok that works... not as clean but cleaner than having arrays + keys
:eyes:
At this point, it looks like this library is abandoned. React 16 came out over 2 years ago, and the last commit here of any kind was over half a year ago.
If you want to use a current version of React with Helmet, your best bet is to either fork it, or use https://github.com/staylor/react-helmet-async which also sheds the problematic dependency on react-side-effect
.
Or, if you have an array of meta data:
{metaData && metaData.map(meta => (
<Helmet>
<meta name={meta.name} content={meta.description} />
</Helmet>
))}
Being able to use fragments would allow me to rewrite this messy piece of code using a map:
<link rel="preconnect" href="https://connect.facebook.net/" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://connect.facebook.net/" />
<link rel="preconnect" href="https://static.hotjar.com/" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://static.hotjar.com/" />
<link rel="preconnect" href="https://www.facebook.com/" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://www.facebook.com/" />
<link rel="preconnect" href="https://www.google-analytics.com/" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://www.google-analytics.com/" />
<link rel="preconnect" href="https://www.google.de/" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://www.google.de/" />
<link rel="preconnect" href="https://www.googletagmanager.com/" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://www.googletagmanager.com/" />
fragments have been around for almost 4 years now, they ever gonna add support?
I'm not sure how others are getting around this, but I have a way I don't hate. Create a function that populates the array of relevant data:
const getFacebookProperties = () => {
let data = [
{ property: "test-1", content: "value-a" },
{ property: "test-2", content: "value-b" },
{ property: "test-3", content: "value-c" },
];
// add additional properties as needed
return data;
}
Then map it in the Helmet:
<Helmet>
{getFacebookProperties().map(c => <meta {...c} />)}
</Helmet>
I'm using it like that:
<BasicSEOHeaders
title="Page Title | Website name"
description="Page description"
/>
import React from "react";
import { Helmet } from "react-helmet";
const BasicSEOHeaders = (props) => {
return (
<Helmet>
<meta property="og:type" content="website" />
<meta property="og:url" content={`${window.location.href}`} />
{props.title && <title>{props.title}</title>}
{props.title && <meta property="og:title" content={props.title} />}
{props.description && <meta name="description" content={props.description} /> }
{props.description && <meta property="og:description" content={props.description} /> }
{(props.title && props.description) && (
<script type="application/ld+json">
{JSON.stringify({
"@context": "https://schema.org",
"@type": "WebPage",
url: window.location.href,
name: props.title,
description: props.description
})}
</script>
)}
</Helmet>
);
};
export default BasicSEOHeaders;
Being able to use fragments would allow me to rewrite this messy piece of code using a map:
<link rel="preconnect" href="https://connect.facebook.net/" crossOrigin="anonymous" /> <link rel="dns-prefetch" href="https://connect.facebook.net/" /> <link rel="preconnect" href="https://static.hotjar.com/" crossOrigin="anonymous" /> <link rel="dns-prefetch" href="https://static.hotjar.com/" />
One solution is to to return an array of JSX elements, instead of wrapping it in fragments:
{map(prefetch, (link, vendor) => (
[
<link key={`preconnect-${vendor}`} rel="preconnect" href={link} />,
<link key={`dns-prefetch-${vendor}`} rel="dns-prefetch" href={link} />
]
))}