First off, amazing tool, just started implementing this on Vogue.com to deal with multiple initial resolution moments as well as the subsequent client updates. We're using a hybrid of a Flux store as well as isomorphic Relay. While we do technically have the data all gathered at an initial point, we would have to manually traverse the Relay internal data structure to retrieve its values. Being able to sequentially set our head as we request data in the template is a much more elegant pattern.
However. This brings me to schema.org JSON definition. If we consider that is has multiple levels of data (publisher, articleSection, [article]headline, author.name, etc), with potentials for defaults (image, @type, etc), then the same functionality of being able to add or replace elements as the render progresses, would work well here as well.
In my use case, I don't have a single point in the render flow in which I "know" all of the properties simultaneously (due to Relay data masking / and not wanting to manually traverse its internal data format). I have a container which renders the author byline (which has author.name), which is a child of the article content container (which has headline), which is a child of the page container (which has articleSection). Even if I did, having to define all the properties at the template layer would be onerous, and provide more opportunities for error.
Thinking through what it would take to allow for extension in an inline script, I come up with the following. If this seems reasonable, I can take on opening a PR.
innerHTML attribute. Potentially it's the current field, or add a new one if we are concerned about monomorphism.innerHTML) with some concept of "id." We will need this to be able to safely merge.type and id are set in a later or nested pattern, AND the payload was structured, then it should merge the properties. -- I assume (though have not tested) that if one wanted to overwrite an inline script in its entirety at the moment, it could not be done, so we don't have to discuss merging vs overwriting.Thanks again for the tool, looking forward to seeing if this approach would be worth the investment.
@brokentone - Thanks for your interest in Helmet. I think a small code example might clear this up more for me. At this point it seems you have a structure of components like so:
<ArticleSection>
<ArticleContent>
<AuthorByline />
</ArticleContent>
</ArticleSection>
Although it seems that you can pass your large Relay data structure down as a prop and pull the data you need from each child component. Or perhaps I am missing something. Your explanation was very detailed, I am just having a bit of trouble visualizing.
I also don't understand the proposal here or how it relates to Helmet. Feel free to open a PR as a follow-up with some more detail. Closing this Issue as an Enhancement that we have no plans to implement.
I have run into the same issue and can elaborate:
Consider the following contrived example... we have an App component that renders some default meta for the site:
const schema = {
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@content': 'http://schema.org',
'@type': 'WebPage',
headline: 'Site title',
}),
};
Then we have an Article component:
const schema = {
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@type': 'NewsArticle',
headline: 'Article headline',
}),
}
Both of these are rendered using:
<Helmet script={[schema]} />
The problem is that these script tags are duplicated, instead of the desired behavior of merging the values. The workaround is to ensure that we don't ever nest these script tags, but then we need to duplicate a bunch of fields across components. The OP is requesting that react-helmet should recognize structured (e.g. type: 'application/json') script tags, and if they share an identifier, merge their values.
This would require a new API, something like (following the more recent API):
<script type="application/ld+json" data-id="schema.org" data-json={{ headline: 'Foo' }} />
For thos like me who have cried their eyes out with this bug of jsonld management, rejoice ! For (I think) I have found a decent enough workaround :
First, plug yourself in on Helmet updates (on your top most Helmet tag) :
<Helmet onChangeClientState={this.trackHelmetChanges}>
Then, in the listener, do some cleaning and refresh helmet instance :
trackHelmetChanges(newState, addedTags, removedTags) {
const JSONLD_TYPE = "application/ld+json";
// console.log('HELMET CHANGED', newState, addedTags, removedTags);
if(newState.scriptTags && newState.scriptTags.length > 1) {
const jsonLDTags = newState.scriptTags.filter(sTag =>聽sTag.type === JSONLD_TYPE);
const otherTags = newState.scriptTags.filter(sTag =>聽sTag.type !== JSONLD_TYPE);
if (jsonLDTags.length) {
otherTags.push(jsonLDTags[jsonLDTags.length - 1]);
newState.scriptTags = otherTags;
// This method is imported from HelmetUtils :
// import {handleClientStateChange} from 'react-helmet/lib/HelmetUtils';
handleClientStateChange(newState);
}
}
}
It did the trick for me, clean and simple. Hope this helps !
Most helpful comment
I have run into the same issue and can elaborate:
Consider the following contrived example... we have an
Appcomponent that renders some default meta for the site:Then we have an
Articlecomponent:Both of these are rendered using:
The problem is that these script tags are duplicated, instead of the desired behavior of merging the values. The workaround is to ensure that we don't ever nest these script tags, but then we need to duplicate a bunch of fields across components. The OP is requesting that
react-helmetshould recognize structured (e.g.type: 'application/json') script tags, and if they share an identifier, merge their values.This would require a new API, something like (following the more recent API):