react-redux-universal-hot-example
react-redux-universal-hot-example copied to clipboard
Server-side rendering question.
I am trying to understand how, in the demo, modifying containers or components (HMR) after the server-side rendering doesn't fire this error.
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:
I noticed something is managing the differences between the initial server-side rendering and any hot-updates there after, even after page refresh.
Any insight or direction would be much appreciated.
(Not sure if it has anything to do with Babel version, I am trying to piece together version 6)
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:
To add to that, if anyone has insight on what mistakes usually cause this error to show up? I keep seeing this error but am not sure where to even start debugging this.
@oyeanuj I have read that extraneous spacing, or any difference whatsoever, can cause this error. I have also read that one way to get around this is through setTimeout
, then a rerender with updated data (But that doesn't seem like a good solution).
( don't know where/how the spans are being generated)
And every other isomorphic example I have looked at has this same problem.
- https://github.com/rackt/redux/tree/master/examples/universal
- https://github.com/acdlite/redux-router/tree/master/examples/server-rendering
- https://github.com/bananaoomarang/isomorphic-redux
@oyeanuj One benefit of server-side rendering is that client-side updates don't have to rewrite the entire DOM; instead, they can hook into the server-rendered DOM and make their changes on top of that. But if your component renders differently depending on whether it's client- or server-side, React can't cheaply update the client-side DOM.
For example, suppose you have a component that hooks into the DOM (and thus requires access to the navigator
or window
global). You can't just import this component unconditionally, because navigator
is not defined on the server-side. So your component render()
function might include the following:
return (
{ canUseDOM && require('client-side-component') }
)
However, this means the server-rendered version of the page does not contain client-side-component
, whereas the client-rendered version does, throwing the error. Granted, it's pretty finicky, and here are a couple common bugs:
- If you use separate templates for the server and client side, have your server-side
render()
wrap everything in an extra single div (ReactDOM creates an extra injection div) - On the client-side template, if you're injecting into
document.documentElement
, usedocument
instead - Make sure you're passing the same props to both versions of the component
- Double-check for dynamically-generated values (timestamps, random numbers, etc.) that might differ between the client and server side
If the issue is something else: the error message displays the line where the diff occurs (both versions), and you can use that as your entry point for debugging. You can start by console logging the innerHTML of your component before and after render()
, and diffing the results.
Hi @sarahlim! That was just the response I was looking for - very helpful, thank you! This is good information to start debugging. One more question based on what you said:
For example, suppose you have a component that hooks into the DOM (and thus requires access to the navigator or window global). You can't just import this component unconditionally, because navigator is not defined on the server-side. So your component render() function might include the following:
I feel like this might be the problem in my case, given the libraries that I might be using. In case of using these external plugins which depend on the DOM, what is a good strategy to avoid the server-side render being overwritten and result in different server and client side code?
@oyeanuj You can trigger a state update with componentDidMount()
like this:
componentDidMount() {
// Re-render for isomorphic purposes
requestAnimationFrame(() => {
this.setState({ appIsMounted: true });
// Do whatever you did in your old componentDidMount here
});
}
render() {
return (
<div className="MyComponent">
{ this.state.appIsMounted && <ClientSideComponent /> }
</div>
);
}
Since we can't render ClientSideComponent on the server side, we choose not to initially render it on the client side either. This way, the initial checksums match between server and client. Then, once our component has finished its initial client-side render -- at which point window
, navigator
, etc. variables will be available -- we can update the state, which triggers a re-render, which finally injects the ClientSideComponent.
Hope that makes sense!
In my apps I can simply check for the CLIENT variable, works like a charm so far...
componentWillUpdate() {
if (__CLIENT__) {
this.someFunction();
}
}
Going with __CLIENT__
is the favourable solution because the webpack strip-loader
can remove dead code from bundles.
Same here on fresh install
I get this error (Warning: React attempted to reuse markup in a container but the checksum was invalid....) as well but don't how to fix this. Any help is appreciated.
- I have a route defined for a city guide like this:
<Route path="/cityguide/:city" component={Cityguide}/>
- Based on the city parameter in the url I update the state in componentWillMount of the Cityguide component:
componentWillMount() {
if (this.props.params.city) {
this.props.updateMapCenter(this.props.params.city);
}
}
This works fine in the browser but on the server it looks like the render method is called before componentWillMount because in the server html (using wget) I see the initial state of city appearing and I see this react warning.
So I need to find a way that the server side rendering will be called after componentWillMount has been executed. I have read that this should in general be the case but not apparently not on the server. Does anyhow know how to fix this?
Thx
@joostaafjes Did you find a solution? If so, please share.
@joostaafjes @chrisblossom have in mind that if you use any lifecycle methods it will only be called on the browser. When the server does renderToString its not rendering a component, its just as its name, a string. So any lifecycle methods will be called on the client. You should call updateMapCenter inside asyncConnect. EDIT: I misspelled the library
The workaround I used is:
render() { // determine city(needed for server side rendering, at client not needed) let realCity = city; if (this.props.params.city) { realCity = this.props.params.city; }
And later call updateMapCenter. So this part is called server side, the componentWillMount is used at the client. I hope to find a nicer solution sometime. @gabriel-miranda: the asyncProps solution is not clear to me. Do I need to include https://github.com/ryanflorence/async-props ?
@joostaafjes are you using the updated version of this repo? I misspelled and corrected my previous comment. Example taken and modified from Widgets.js
@asyncConnect([{
promise: ({store: {dispatch, getState}}) => {
// check if the data you want is loaded
if (!isLoaded(getState())) {
// if not loaded dispatch the action
return dispatch(loadSTuff());
}
}
}])
And in your reducer you have a flag to fill loaded
, loading
and error
, then you create that function isLoaded()
export function isLoaded(globalState) {
return globalState.stuff && globalState.stuff.loaded;
}
Check out this repo, the solution its already here. If you need some code to execute only in the client use componentDidMount
else do it this way.
i have solution for you, you need to change all event what can modification DOM after componentDidMount
A common issue I've seen in my project. Is if you're storing data in user cookies or localstorage you will get this issue.
I've some objections to examples above that rely on waiting then re-rendering on the client. It can cause a flash of bad content.
I'm having same warning. But in my case, my app using facebook draft-js to display / edit content of a post. the component look like this.
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import Helmet from 'react-helmet';
import { asyncConnect } from 'redux-async-connect';
import {isLoaded as isPostsLoaded, loadPosts} from 'redux/modules/post';
import { Editor, EditorState, convertFromRaw } from 'draft-js';
@asyncConnect([{
deferred: true,
promise: ({store: {dispatch, getState}}) => {
if (!isPostsLoaded(getState())) {
return dispatch(loadPosts());
}
}
}])
@connect(
state => ({posts: state.posts || []}))
export default class PostsList extends Component {
static propTypes = {
posts: PropTypes.object,
}
render() {
const {posts} = this.props;
const styles = require('./Posts.scss');
return (
<div className={styles.posts}>
<Helmet title="All Posts"/>
<h1>A list of recent posts</h1>
<ul>
{posts.data && posts.data.map(post=>
<li key = {post._id}>
<SinglePost posts ={post}/>
</li>
)}
</ul>
</div>
);
}
}
export class SinglePost extends Component {
static propTypes = {
posts: PropTypes.object,
}
render() {
const {title, content} = this.props.post;
const rawContent = convertFromRaw(JSON.parse(content)); // content is stored in DB in text
const editorState = EditorState.createWithContent(rawContent);
return (<div>
<h4>{title}</h4>
<div>
<Editor
readOnly="true"
editorState = {editorState}/>
</div>
</div>);
}
}
having this Warning in console:
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:
(client) "true" data-editor="9r2ss" data-offset-k
(server) "true" data-editor="eg2hd" data-offset-k
tried above solutions but it seems that none of the solutions can solve my problem. Any idea how to use SSR in this case ?
@sarahlim
Created a higher order component for isMounted. Thought it might be useful, I seem to be using your trick a lot.
import React, { Component } from 'react';
// React Higher Order Component
// https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.tzxn3jc1x
export default IsMounted => class extends Component {
state = {
isMounted: false,
}
async componentDidMount() {
// https://github.com/erikras/react-redux-universal-hot-example/issues/769
requestAnimationFrame(() => {
this.setState({ isMounted: true });
});
}
render() {
return (<IsMounted
{...this.props}
{...this.state}
/>);
}
};
Use it like this:
import React, { Component, PropTypes } from 'react';
import IsMounted from '../IsMounted/index.jsx';
class ExampleComponent extends Component {
static propTypes = {
isMounted: PropTypes.bool.isRequired,
}
render() {
if (!this.props.isMounted) {
return null;
}
return (<div>I'm Mounted</div>);
}
}
export default IsMounted(ExampleComponent);