-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Document that you can't rely on React 16 SSR patching up differences #10591
Comments
The styles not being patched up is expected. That's an intentional breaking change. The idea behind that is that most sites stabilize once they have a solid SSR infra in place and therefore these things don't happen, yet they would have to spend all that time validating unnecessarily when they know it's not going to change. So it's an optimization. The second part here should not happen though. It should not be inserted inside another parent. Do you think you'd be able to provide a smaller reduced example e.g. in a JSFiddle? It's a bit difficult to debug on a fully deployed site. |
In development mode you should see a warning, btw. Are you not seeing the warning? |
I updated the repo with an example of what I mean. Here is a code snippet: import React from 'react'
export default function renderer (isServer, render) {
return render(<AppView isServer={isServer} />, !isServer && document.getElementById('root'))
}
class AppView extends React.Component {
render () {
const isServer = this.props.isServer
return (
<div>
{
isServer && <ServerComponent />
}
<ListComponent />
</div>
)
}
}
class ServerComponent extends React.Component {
render () {
const styles = {
server: {
backgroundColor: 'red'
}
}
return <div style={styles.server} />
}
}
class ListComponent extends React.Component {
constructor (props) {
super(props)
this.getList = this.getList.bind(this)
}
getList () {
const styles = {
el: {
width: '200px',
height: '200px',
margin: '20px',
backgroundColor: 'blue'
}
}
return Array.from((function * () {
for (let i = 0; i < 10; i++) {
yield <div style={styles.el} key={i} />
}
})())
}
render () {
const styles = {
client: {
backgroundColor: 'green',
display: 'flex',
flexWrap: 'wrap'
}
}
return (
<div style={styles.client}>
{ this.getList() }
</div>
)
}
} And this will happen:
So you said the styles won't be patched, which is an optimization, but in this case I replace an entire The problem on my website is that I make assumptions about screen size, which might not be true. Is there a stable method to get screen size on initial server rendering? Can I use the newly introduced SSR streaming method to get this working? |
Ah. The reason this happens is because we try to reuse the structure as much as possible. Because the first item is a div in both cases we try to reuse it. That's why it retains its styles. If it was a different tag name it would patch it up but it's risky to rely on this. You can build a little infra that passes screen size to the server as a separate request but that might be overkill. If you don't have screen size on the server you will server render the wrong output which could lead to flashes on the content when it finally restores the JS on the client. If your app is resilient to that, then there is a way to model that on the client. You can render the initial mount with the server render screen size. Then in componentDidMount you can read the real screen size and if it doesn't match up with your guess, then you rerender by setting state. class App extends React.Component {
state = { screenSize: DEFAULT_SIZE };
componentDidMount() {
this.setState({ screenSize: getRealScreenSize() });
}
} |
I also have a use case in which I intentionally have the server render something slightly different than the client will render, and it's affected by this change in React 16 as well. I have a component that renders a feed of items. On the server I render one page of items at a time, with conventional "Prev" and "Next" pagination links. On the client I render an infinitely scrollable virtualized list. Rendering a conventional paginated list on the server helps improve SEO by giving search bots actual links to follow to subsequent pages and also ensures that clients with JS disabled can still paginate through the list. There's no visible flash when a JS-capable client hydrates the list because the only visible difference (pagination links) will typically be below the fold, so this should work out well. However, React 16 reuses certain server-rendered elements without removing their I understand that it's generally not advisable for server-rendered content to differ from client-rendered content, but this was a conscious choice made with full awareness of the disadvantages. I expected a slight performance penalty, but didn't expect broken output. If this scenario really is considered unsupported in React 16 and there are no plans to allow developers to opt into full validation when hydrating server HTML, then it seems like the dev mode warning should be an outright error in order to prevent subtle breakage from slipping into production. This change should probably also be called out prominently in the release notes, since it could unexpectedly break existing code that relies on the old behavior. |
But, yea this will definitely need to be prominent release note and maybe a separate blog post. What's your mechanism for silencing the warning in 15? Maybe we can target that as a way to inform you? We rely pretty heavily on Note that hydration isn't just a performance optimization but a way to preserve state in things like forms and animations. I'm going to add a way to suppress the diff warning for individual nodes. At least for text content differences. We could potentially expand that to direct child elements of that node too. That way you could work around it by using different tag names on the container element. |
Basically I close my eyes and put my fingers in my ears and yell "you're not my supervisor!" 🙈 I took another pass at my list component using the two-phase render approach you recommended above and it works well, so I think that'll be good enough to cover my use case. Thanks! My guess is I'm not the only one who's gotten used to ignoring that warning, so I'll try to help get the word out about the change. |
My case of use. Our server does not know about authorization. And when transitions between applications, we quickly restore part of the data of the authorized zone. In this case, patch html and story. Thus, there is a fast switching between applications. |
Btw, if you relied on React to clear out the DOM for you in 15 you can still do that explicitly in 16. Just do |
It sounds like the action item here is to update docs/release notes and possibly write a blog post about this change, showing people how to migrate their code if they run into this issue. |
hi @sebmarkbage, |
@sebmarkbage, thanks for your thorough answers here. We're running into this too and your comments helped us understand what's going on. That said, in all honesty, I don't completely see how this is not considered a bug. It's very hard and messy to ensure that what's rendered server-side and client-side are 100% equal. For instance, what if a component displays something time-based and the client clock isn't millisecond-precise equal to the server clock? (e.g. always). In our case, seemingly random (but consistent) items in a list suddenly get some child div content swapped. Obviously everything can be worked around by copying all and every piece of global state from the server to the client (and even ensuring that eg the same millisecond time is used in the entire render call), but isn't that major overkill? Obviously, by using (in all honesty I think the screen size example from @Tarnadas is a nice example too) |
@eteeselink Typically time based things are only things like time stamps where only the text changes, not the order of items. Those will work fine since the text content in those will be patched up. We're also planning on adding the ability to ignore the warning in that specific special case. A rare but plausible scenario is that you have a list or something that depends on time to determine the order. E.g. something that conditionally pushes something on the top of a list if some time has elapsed. Or changes the the class name based on time elapsing. These cases are very theoretical though. I haven't seen one yet. You probably want to represent these as some kind of global data state change or transition anyway. E.g. to indicate that an alarm was fired so you know to ignore it. Even in the worse case scenario you don't need to send all state down to solve time based issues, use virtualized time. Note that if you use the "current" time you can get "tearing" across the screen even if you're only rendering on the client since not all components render at exactly the same time, they can end up with different time stamps for the same render. This can be more prevalent with async rendering where the time to render an entire update can be longer. A bigger issue would be that data fetching from backend end points can return different data. That's why most systems that use React server rendering seriously send the data along with the initial request anyway. E.g. see Next.js's This avoids two trips to backend services which avoids unnecessarily hitting those services with 2x the ratio. You already have the data loaded so you might as well tack it on to the end of the initial request pipe. I agree that this is quite complex but it seems like it is ultimately the best architecture for other reasons. Now, in theory we could go back to the model of comparing everything and trying to patch things up. That would make things significantly slower for everyone that does the above architecture anyway for no reason. In theory we could have two modes. However, even in that mode, you'll still have cases where state such as text input and scroll position changes unexpectedly to the user as the result of inconsistent data. This also doesn't seem ideal. |
Also ran into this issue after upgrading to React 16. Using This broke after upgrading, since it would render the mobile view with the Basic example: <Media query="(min-width: 1025px)">
{matches =>
matches ? (
<div className="desktop">Desktop size</div>
) : (
<div className="mobile">Mobile size</div>
)}
</Media> I solved it for now by using the same CSS class and using media queries to modify the container. |
If you want to render something different on the client, you can set state in |
Thanks for the fast response! I see now that this behavior was called out in What's New With Server-Side Rendering. I have a couple suggestions, both of which I'd be willing to open a PR for:
|
Thank you for filing this issue! 😄 The documentation and source code for reactjs.org now lives in a different repository: reactjs/reactjs.org. (For more info on why we made this move, see issue #11075.) This issue is a bit tricky because it seems like there are a couple of calls to action. For the documentation bit, I've created an issue in the new repo: reactjs/react.dev#25 Someone (@dustinsoftware?) should open a new issue for the proposed warning message modifications so we can continue the discussion there. I will close this issue out but I'll be sure to reference the discussion thread above in the new issue. |
I've just stumbled on this. In my own case, my app essentially just renders tons of anchor tags. However, post <li>
<a href="http://go.theregister.com/feed/www.theregister.co.uk/2017/11/14/fcc_commissioner_continues_pushing_for_puerto_rico_hearings/"
class="story-title"
data-title="FCC commish wants more than one-page updates on recovery Jessica Rosenworcel, one of the commissioners at America&#039;s broadband watchdog the FCC, has reiterated her call for hearings into what is happening with communications on the hurricane-stricken island of Puerto Rico.…"
>How about that US isle wrecked by a hurricane, no power, comms... yes, we mean Puerto Rico</a>
</li> Post- <li>
<a href="http://go.theregister.com/feed/www.theregister.co.uk/2017/11/14/oneplus_backdoor/"
class="story-title"
data-title="Who left &#039;wipe the engineering toolkit&#039; off the factory checklist? Updated An apparent factory cockup has left many OnePlus Android smartphones with an exposed diagnostics tool that can be potentially exploited to root the handsets.…"
>How about that US isle wrecked by a hurricane, no power, comms... yes, we mean Puerto Rico</a>
</li> For users this is obviously deeply confusing! It feels like this kind of failed reconciliation is a real issue. At the moment it seems that the only way to get around this is to stop rendering links / anchor tags on the server entirely. That will force the client to render them correctly. This rather removes the benefit of SSR though (at least for my own case where the app is not much more than a glorified collection of links). My takeaway from this is that rendering lists of links / anchor tags with SSR may then leave you with links that point to different locations when Could you confirm that's intended behaviour please? I just want to be sure I haven't misunderstood the intention of this change. In case it's relevant the app is here and the source code is here. |
React does not introduce any differences on its own, whether in text content, anchor tags, or anywhere else. But if your components return different things on client and server, React won't attempt to fix the difference. You are expected to return the same thing on the client and the server. Otherwise it's a bug in your app. React warns you about mismatches in development, and you should fix if you see any. This thread is getting long and I don't think it's a good place to track any new problems. If you believe that what you see might be a bug in React please file an issue. The |
With SSR being loaded on client side, there are various wrong behaviors if the server's HTML differs from the client's HTML.
For a minimal example, I created this repository.
Here is a code snippet:
In the example I render a CSS background color of red for the server and green for the client. I force a difference by the server's and client's HTML with the
isServer
property.With React 15, everything works as expected: the server renders a red background, but the client corrects it to green. With React 16 however the background stays the same, but the text changes as expected.
There are probably other similar behaviors. For example I found out about this bug, because I was conditionally rendering a complete component like so:
It becomes even more weird, because if there is additional JSX after that conditional rendering, it would render that additional JSX as if it is inside that
MyComponent
becomes
if
someCondition === true
on server side andsomeCondition === false
on client side.You can see this behavior on my website: http://smmdb.ddns.net/courses
Open Chrome Dev Tools and lower the width until you get the mobile view, then reload the page and see how the list is wrapped inside another Component.
The text was updated successfully, but these errors were encountered: