Do you want to request a feature or report a bug?
Report a Bug
What is the current behavior?
Hi!
I have just come across what _looks_ like a bug with react reconciliation when rendering in a browser over an existing version of the same component (rendered on the server).
This appears to only happen in react and react-dom 16, I have tried 16.4.1, 16.4.0, and 16.0.0 and all display the same behaviour. Whereas react & react-dom 15 seem to work as expected.
If we take this (simple but rather contrived) component as an example:
Header.js
import React from 'react';
export default (props) => {
const classes = `header--${props.name}`;
return (
<div className={classes}>
<p>{props.name}</p>
</div>);
}
Then server side render it like so:
import {renderToString} from 'react';
import Header from '../Header';
renderToString(<Header name="leonardo" />);
we get something like the below:
<div id="the-mount">
<div class="header--leonardo"}>
<p>leonardo</p>
</div>
</div>
Then, later, in the browser:
import ReactDom from 'react-dom';
import Header from '../Header';
const mount = document.getElementById('the-mount');
ReactDom.render(<Header name="donatello" />, mount);
the component updates to:
<div id="the-mount">
<div class="header--leonardo"}>
<p>donatello</p>
</div>
</div>
Note, the class value has not changed despite being driven by the same prop as the <p> content which _has_ updated, and so is now incorrect.
What is the expected behavior?
I would expect to see the following after the render call in the browser:
<div id="the-mount">
<div class="header--donatello"}>
<p>donatello</p>
</div>
</div>
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
I can replicate this in multiple versions of react and react-dom 16, however I see the expected behaviour in 15.
Seems to work here https://jsfiddle.net/xsL5ef4c/
@nmain There is a difference there as you are using hydrate rather than a renderToString, i'm not well versed in how that translates to the SSR -> client rendering.
Bonus points for using the correct colours though. 馃槃
I've put together an example closer to what I'm doing here: https://github.com/craigkj/react-class-reconcilliation-bug and I can see the bug replicated there.
It _does_ introduce babel, webpack and so on, so there could be an issue with the set up... although im struggling to find it.
Screenshot:

Although curiously React tools (in chrome) thinks the className has updated

Firefox and Safari show the same.
Installing react and react-dom 15 makes the above example work as expected.
Ok, so I think the difference here is that 16 is requiring hydrate to be called for the client side hydration (and the props have to be the same) whereas that was done with ReactDom.render(...) and versions prior to 16 were more sensitive to changes (perhaps this reduction in checking attributes is one of the ways speed has been improved... 馃)
If I change the client side code to first hydrate with the same props as the SSR then it works as expected:
import React from 'react';
import ReactDom from 'react-dom';
import Header from './header';
const mount = document.getElementById('the-mount');
ReactDom.hydrate(<Header name="Leonardo" />, mount);
setTimeout(() => {
ReactDom.render(<Header name="Donatello" />, mount);
}, 2000);
However this obviously results in two calls for one render.
The hydrate docs has some notes to this effect (https://reactjs.org/docs/react-dom.html#hydrate)
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them. In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive.
If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like this.state.isClient, which you can set to true in componentDidMount(). This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. Note that this approach will make your components slower because they have to render twice, so use it with caution.
So this looks like intentional, but not ideal behaviour. Its certainly different from the equivalent functionality in previous versions.
In the case where you don't care about your server render at all (so to avoid having to hydrate, and two pass rendering) I would assume that the answer would be to remove the contents of the container yourself before calling the render? Having to hydrate something that I want to wholesale replace seems inefficient, especially if the side effects are potentially nasty.
For example, if the hydrate is done with different (mismatched) data, the className is not updated but the content is (as per hydrate docs above), however there is an additional side effect that then causes that className to not be replaced for future render calls with the same (new) content afterwards - this could be nasty unless you are passing your entire SSR state to the client for hydration, even when you know that some is irrelevant. Example:
import React from 'react';
import ReactDom from 'react-dom';
import Header from './header';
const mount = document.getElementById('the-mount');
ReactDom.hydrate(<Header name="Donatello" />, mount);
setTimeout(() => {
ReactDom.render(<Header name="Donatello" />, mount);
}, 2000);
Perhaps there is a better workaround for this than clearing parts of the dom manually, or maybe the full state hydration is a cost you have to pay.
Yes, I think that's the idea. In my example, I was assuming the serverside rendering had already happened, and so just included that markup in the static html of the fiddle. Then, the first clientside call should be a hydrate, not a render.
In the case where you don't care about your server render at all (so to avoid having to hydrate, and two pass rendering) I would assume that the answer would be to remove the contents of the container yourself before calling the render? Having to hydrate something that I want to wholesale replace seems inefficient, especially if the side effects are potentially nasty.
Why do you want to completely throw away server-rendered markup? Two-pass rendering is likely much faster than throwing out all the markup, assuming that the client-rendered markup is _mostly_ the same.
Before ReactDOM.hydrate existed you would use render to hydrate. This behavior will be removed in v17, but currently React uses a heuristic to detect if render is being called with server-rendered markup. If so, it logs a warning and still tries to hydrate.
If you want to avoid this you'll need to manually remove all the markup from your container before rendering.
@aweary
In this case the server rendered markup and the client rendered are quite different for this component, although this might be quite an edge case. The data that powers the SSR isn't passed to the client in a store as its only ever going to be replaced wholesale, however to replace wholesale correctly I now need to hydrate, which requires the data passed in a store, so that I can then throw it, the SSR, and the hydration, away by calling render.
Appreciate that this is likely an edge case that I found while prototyping, so might not manifest itself too often in the real world, but I think theres a horrible gotcha waiting in there for a few people in the future.
Anyway, thanks for the replies :)
I've just come across this problem and my use case is as follows:
SSR produces a list of items which can be favourited on the client. The favourites data is maintained only on the client in localStorage, so the SSR produces checkboxes which are unchecked, and the client sets the checked property on the checkbox input according to what's favourited in localStorage. This bit works ok.
The problem is that there is a className computed depending on whether the item is favourited (checked vs unchecked) because this changes other visual characteristics of the component. The className is not refreshed during the hydrate, so the item lacks the visual characteristics of a favourited item. Toggling the checkbox causes the className to be recomputed.
My work around is going to be to suppress the render of the checkbox control on the server, and use a second pass render (via useEffect) on the client to insert the checkbox. This isn't ideal, because the className -could- appear on the item component which must be rendered during SSR, but I can rejig things as a workaround.
So to be clear, I'm not 'throwing away' server rendered markup, I'm embellishing it based on state held in the client. This seems like a reasonable use case and it seems incorrect for React to not deal with this situation.
@gaearon This component is rendered on server with some props and created a static markup, Now it has been rendered with different props on client so I think it should be updated again. This will likely happens to anyone who use SSR and Persisted Redux stores and render attributes (usually classNames) based on persisted data on client (or anything that does not match with server).
I'm mentioning this since many people use import { persistStore } from 'redux-persist'; to persist data in local storage.
So to be clear, I'm not 'throwing away' server rendered markup, I'm embellishing it based on state held in the client. This seems like a reasonable use case and it seems incorrect for React to not deal with this situation.
Doing the same (embellishing based on client state) and I agree completely - seems like a really scary assumption to make that attributes have to match 1:1 and debugging this was extremely hard
Ok, so I think the difference here is that 16 is requiring
hydrateto be called for the client side hydration (and the props have to be the same) whereas that was done withReactDom.render(...)and versions prior to 16 were more sensitive to changes (perhaps this reduction in checking attributes is one of the ways speed has been improved... 馃)If I change the client side code to first hydrate with the same props as the SSR then it works as expected:
import React from 'react'; import ReactDom from 'react-dom'; import Header from './header'; const mount = document.getElementById('the-mount'); ReactDom.hydrate(<Header name="Leonardo" />, mount); setTimeout(() => { ReactDom.render(<Header name="Donatello" />, mount); }, 2000);However this obviously results in two calls for one render.
The hydrate docs has some notes to this effect (https://reactjs.org/docs/react-dom.html#hydrate)
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them. In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive.
If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like this.state.isClient, which you can set to true in componentDidMount(). This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. Note that this approach will make your components slower because they have to render twice, so use it with caution.
So this looks like intentional, but not ideal behaviour. Its certainly different from the equivalent functionality in previous versions.
In the case where you don't care about your server render at all (so to avoid having to hydrate, and two pass rendering) I would assume that the answer would be to remove the contents of the container yourself before calling the render? Having to hydrate something that I want to wholesale replace seems inefficient, especially if the side effects are potentially nasty.
For example, if the hydrate is done with different (mismatched) data, the className is not updated but the content is (as per hydrate docs above), however there is an additional side effect that then causes that className to not be replaced for future render calls with the same (new) content afterwards - this could be nasty unless you are passing your entire SSR state to the client for hydration, even when you know that some is irrelevant. Example:
import React from 'react'; import ReactDom from 'react-dom'; import Header from './header'; const mount = document.getElementById('the-mount'); ReactDom.hydrate(<Header name="Donatello" />, mount); setTimeout(() => { ReactDom.render(<Header name="Donatello" />, mount); }, 2000);Perhaps there is a better workaround for this than clearing parts of the dom manually, or maybe the full state hydration is a cost you have to pay.
The suggested change in componentDidMount() does the trick. Thanks!
Most helpful comment
I've just come across this problem and my use case is as follows:
SSR produces a list of items which can be favourited on the client. The favourites data is maintained only on the client in localStorage, so the SSR produces checkboxes which are unchecked, and the client sets the checked property on the checkbox input according to what's favourited in localStorage. This bit works ok.
The problem is that there is a className computed depending on whether the item is favourited (checked vs unchecked) because this changes other visual characteristics of the component. The className is not refreshed during the hydrate, so the item lacks the visual characteristics of a favourited item. Toggling the checkbox causes the className to be recomputed.
My work around is going to be to suppress the render of the checkbox control on the server, and use a second pass render (via useEffect) on the client to insert the checkbox. This isn't ideal, because the className -could- appear on the item component which must be rendered during SSR, but I can rejig things as a workaround.
So to be clear, I'm not 'throwing away' server rendered markup, I'm embellishing it based on state held in the client. This seems like a reasonable use case and it seems incorrect for React to not deal with this situation.