staylor / react-helmet-async

Thread-safe Helmet for React 16+ and friends
Apache License 2.0
2.09k stars 154 forks source link

empty title tags when rendering component #80

Closed devcat22 closed 4 years ago

devcat22 commented 4 years ago

Hello!

I'm trying to swap out my usage from react-helmet to use react-helmet-async, but I am noticing that my title tags are empty every single time.

Here's how what I'm doing it:

On the client, the component to be rendered.

        <HelmetProvider>
            <Helmet>
                <title>Hello</title>
            </Helmet>
        </HelmetProvider>

On the server

const withHelmetProvider = (Component, context) => ({ ...props }) => (
    <HelmetProvider context={context}>
        <Component {...props} />
    </HelmetProvider>
);

const helmetContext = {};
ReactDOM.renderToString(withHelmetProvider(Component, helmetContext));
const { helmet } = helmetContext;
console.log(helmet.title.toStringI());

I see that every time the server logs, the tag is empty:

<title data-rh="true"></title>

What are some possible causes for this? I've verified the following:

I wanted to ask what could be some common causes for it?

I've made sure that the helmetContext is living for the entire lifecycle of the request.

devcat22 commented 4 years ago

EDIT: Just using console.log statements now. Ignore the rant.

It looks like I'm seeing that on the server, we add two instances of Helmet after adding the following diff:

diff --git a/src/Provider.js b/src/Provider.js
index 9e76735..018830f 100644
--- a/src/Provider.js
+++ b/src/Provider.js
@@ -43,6 +43,7 @@ export default class Provider extends Component {
     helmetInstances: {
       get: () => this.instances,
       add: instance => {
+        console.log('**** adding helmet instance ***** ');
         this.instances.push(instance);
       },
       remove: instance => {
devcat22 commented 4 years ago

So it looks like while it's rendering on the server (during renderToString), it does recognize the tags, after applying more logs:

**** state to map onto server ****
{ baseTag: [],
  bodyAttributes: {},
  defer: true,
  encode: true,
  htmlAttributes: {},
  linkTags: [],
  metaTags: [],
  noscriptTags: [],
  onChangeClientState: [Function],
  scriptTags: [],
  styleTags: [],
  title: 'Hello',
  titleAttributes: {} }

Just can't tell why the title property on the helmet instance on the context object is always empty, after renderToString.

devcat22 commented 4 years ago

Sigh. Even the initial release doesn't work. I'm not sure what's going on here. @staylor (or anyone?) are there any clues as to what could be the issue?

staylor commented 4 years ago

what version of React?

devcat22 commented 4 years ago

React is at 16.8.6.

EDIT: I've lowered react to 16.8.6

devcat22 commented 4 years ago

So I'm finding that it only works if the render to string directly has a Helmet tag.

This example works fine.

const helmetContext = {};
const Component = ...
ReactDOM.renderToString(
    <HelmetProvider context={helmetContext}>
        <Helmet>
            <title>Hello 2</title>
        </Helmet>
        <Component {...props}/>
    </HelmetProvider>
);
const { helmet } = helmetContext;
console.log(helmet.title.toString()); // logs <title data-rh="true">Hello 2</title>

But when Component has the tag, it's able to find it.. but it's empty in context.

devcat22 commented 4 years ago

Ok, I think see what the issue is.. it looks like if Component has HelmetProvider, it'll be empty on the server

// component.js -- doesn't work, logs empty title, <title data-rh="true"></title>
export default (props: Props) => (
    <HelmetProvider>
        <Helmet>
            <title>yo this is a cool title</title>
        </Helmet>
    </HelmetProvider>
);

But if it just has Helmet, it's able to log it out fine, as expected.

// component.js -- works fine
export default (props: Props) => (
        <Helmet>
            <title>yo this is a cool title</title>
        </Helmet>
);

The remaining issue is on the client, it doesn't work because its expecting a provider. You'll get the following error message:

"TypeError: Cannot read property 'add' of undefined",

but if you lift the provider high enough, I guess it should work.

devcat22 commented 4 years ago

Going to close out this issue. It looks like the problem was two Providers competing with each other.