react-redux-universal-hot-example icon indicating copy to clipboard operation
react-redux-universal-hot-example copied to clipboard

Server-side rendering question.

Open chemoish opened this issue 8 years ago • 17 comments

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.

screen shot 2016-01-04 at 8 17 29 pm

screen shot 2016-01-04 at 8 17 09 pm

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)

chemoish avatar Jan 05 '16 04:01 chemoish

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 avatar Jan 05 '16 04:01 oyeanuj

@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

chemoish avatar Jan 05 '16 05:01 chemoish

@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:

  1. 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)
  2. On the client-side template, if you're injecting into document.documentElement, use document instead
  3. Make sure you're passing the same props to both versions of the component
  4. 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.

sliminality avatar Jan 05 '16 21:01 sliminality

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 avatar Jan 05 '16 22:01 oyeanuj

@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!

sliminality avatar Jan 05 '16 22:01 sliminality

In my apps I can simply check for the CLIENT variable, works like a charm so far...

componentWillUpdate() {
  if (__CLIENT__) {
    this.someFunction();
  }
}

janhoogeveen avatar Jan 08 '16 16:01 janhoogeveen

Going with __CLIENT__ is the favourable solution because the webpack strip-loader can remove dead code from bundles.

trueter avatar Jan 12 '16 02:01 trueter

Same here on fresh install

gabriel-miranda avatar Jan 21 '16 11:01 gabriel-miranda

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.

  1. I have a route defined for a city guide like this: <Route path="/cityguide/:city" component={Cityguide}/>
  2. 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 avatar Jan 30 '16 22:01 joostaafjes

@joostaafjes Did you find a solution? If so, please share.

chrisblossom avatar Mar 27 '16 00:03 chrisblossom

@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

gabriel-miranda avatar Mar 27 '16 13:03 gabriel-miranda

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 avatar Mar 27 '16 14:03 joostaafjes

@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.

gabriel-miranda avatar Mar 27 '16 22:03 gabriel-miranda

i have solution for you, you need to change all event what can modification DOM after componentDidMount

yussan avatar Sep 09 '16 03:09 yussan

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.

krismeister avatar Nov 03 '16 21:11 krismeister

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 ?

ducnvhn avatar Feb 14 '17 09:02 ducnvhn

@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);

PetrochukM avatar Mar 21 '17 05:03 PetrochukM