Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QUESTION: How do I pass data to a child container when using server rendering #1

Closed
gtamir opened this issue Aug 9, 2016 · 12 comments
Labels

Comments

@gtamir
Copy link

gtamir commented Aug 9, 2016

Hi,
Thanks for the post and the example.
I'm using your boilerplate code in my app and the only thing missing is that I need to pass some data (result of an ajax call) to one of my containers (to fix SEO issues).

The app's stucture is something like this:

<App>
    ...
    <UsersPage>
    ...
</App>

and I need to pass the data to the the UsersPage container.
There are a lot of examples out there, but most contain usage of jade or other node related objects and your example is much more clear.

Could you assist?

BTW, I'm not using the Redux part just simple data binding...

Thanks in advance.

@justinjung04
Copy link
Owner

justinjung04 commented Aug 9, 2016

Hello,
Thank you for your feedback. I really appreciate it. I will try my best to explain a possible solution step by step.

In order for your ajax data to be reflected in your initial render:

  1. Your data must be prepared prior to the render
  2. When rendering, your component must have a way to refer to the data

Step 1 will introduce an overhead to your server request, since we have to 'wait' until the data is retrieved from the API call.

There could be many ways to achieve Step 2. One of the simplest solution is to pass your data as a prop to App, which will be passed down to UsersPage.

If I put these together, my code will be updated as follows:

// middleware.js
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    ...
    } else if(process.env.NODE_ENV == 'production') {
        myAjaxCall((ajaxResult) => {
            res.status(200).send(`
                <!doctype html>
                <html>
                    <header>
                        <title>My Universal App</title>
                        <link rel='stylesheet' href='bundle.css'>
                    </header>
                    <body>
                        <div id='app'>${renderToString(<App data={ajaxResult}/>)}</div>
                        <script src='bundle.js'></script>
                    </body>
                </html>
            `);
        });
    }
    ...
});

// app.js
render() {
    return (
        ...
        <UsersPage data={this.props.data} />
        ...
    );
}

Please let me know if this resolves the issue :)

@gtamir
Copy link
Author

gtamir commented Aug 9, 2016

Big thanks for the quick reply!

When I switched to rendering the App object (instead of RouterContext) I start receiving this error:

Warning: React attempted to reuse markup in a container
but the checksum was invalid. This generally means that you are using
server rendering and the markup generated on the server was not what the client was expecting.
React injected new markup to compensate which works but you have lost many of the benefits of server rendering.
Instead, figure out why the markup being generated is different on the client or server:...

The other thing is that I needs to pass the props.data to the rendered children components.
I don't render the UsersPage directly but instead use:{this.props.children}.
I saw an example saying it should be done like this: {React.cloneElement(this.props.children, this.props)}
but it fails since the children object is undefined. I assume it's somehow related to the whole server rendering thing.

And also, when I look at the network tab in the browser it still seems like the actual content doesn't get rendered, so I guess it's not working yet...

It this is getting too specific for you, let me know and I'll just try to figure it out on my own :)

Thanks again.

@gtamir
Copy link
Author

gtamir commented Aug 9, 2016

BTW, I saw this post which seems to be trying to solve the same problem, but I'm still trying to understand if it can be done with just using react...

@justinjung04
Copy link
Owner

justinjung04 commented Aug 9, 2016

Oops, I think I made some mistake!

  1. When html gets rendered from the client side, this.props.data is undefined. Thus, I should use a global variable to store the ajaxResult, let my root component read it and pass it down as a prop.
  2. Passing down a prop in RouterContext is a little different.

You can update the code as follows:

// middleware.js
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    ...
    } else if(renderProps) {
        myAjaxCall((ajaxResult) => {
            if(process.env.NODE_ENV == 'development') {
                res.status(200).send(`
                    <!doctype html>
                    <html>
                        <header>
                            <title>My Universal App</title>
                            <link rel='stylesheet' href='bundle.css'>
                        </header>
                        <body>
                            <div id='app'></div>
                            <script>
                                window.__AJAX_RESULT__ = ${JSON.stringify(ajaxResult)};
                            </script>
                            <script src='bundle.js'></script>
                        </body>
                    </html>
                `);
            } else if(process.env.NODE_ENV == 'production') {
                res.status(200).send(`
                    <!doctype html>
                    <html>
                        <header>
                            <title>My Universal App</title>
                            <link rel='stylesheet' href='bundle.css'>
                        </header>
                        <body>
                            <div id='app'>${renderToString(
                                <RouterContext
                                    {...renderProps}
                                    createElement={(Component, props) => 
                                       <Component data={ajaxResult} {...props} />
                                    }
                                />
                            )}</div>
                            <script>
                                window.__AJAX_RESULT__ = ${JSON.stringify(ajaxResult)};
                            </script>
                            <script src='bundle.js'></script>
                        </body>
                    </html>
                `);
            }
        });
    }
    ...
});

// routes.js
class App extends Component {
    render() {
        return (
            <div>
                {(this.props.data) 
                    ? React.cloneElement(this.props.children)
                    : React.cloneElement(this.props.children, {data: window.__AJAX_RESULT__})
                }
            </div>
        );
    }
}

export default (
    <Route path='/' component={App} >
        <IndexRoute component={Home} />
        <Route path='page' component={Page} />
    </Route>
);

Then you shoud be able to read ajaxResult from your Home and Page components with this.props.data.
I've done a quick verification so the above solution should work. If not, please let me know :)

@gtamir
Copy link
Author

gtamir commented Aug 9, 2016

Justin, looks like it's working!

I'm going to play with it a little tomorrow just to make sure I'm not missing anything. I'm somewhat new at this...
I might have a few followup questions, so let's keep the issue open for now.

Thanks a lot!
Gai

@justinjung04
Copy link
Owner

No worries :) Let me know when you want to close it.

@gtamir
Copy link
Author

gtamir commented Aug 10, 2016

Justin,
Correct me if I'm wrong but I think that using the global variable in production mode is redundant since I'm already getting the data in the props.data. Right?

Also, I have a few additional questions concerning the implementation that are not relevant here. Is it ok if I send you a separate email with my questions?

Thanks,
Gai

@justinjung04
Copy link
Owner

The global variable is necessary for storing the ajax result that will be used as a data prop for client-side rendering. Client-side rendering is a whole new render from the scratch.
You can always email me justinjung04@gmail.com!

@gtamir
Copy link
Author

gtamir commented Aug 11, 2016

Understood, but in PRODCUTION mode it's not being used. The App component only uses it in debug mode (when the props.data is unavailable)

@justinjung04
Copy link
Owner

No, the initial render in production mode is just a string, so props.data would still be unavailable. That's why I said client-side rendering is a whole new render from scratch (it doesn't get any states, props or event handlers from server-side rendering)

@gtamir
Copy link
Author

gtamir commented Aug 11, 2016

OK. I think I understand now.
Thanks!

@justinjung04
Copy link
Owner

No worries :) I'll close the issue then. You can open a new issue or email me for other questions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants