question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[SSR] Mismatch in className suffix when SSRing a component when the first rendering pass doesn't render the component

See original GitHub issue
  • The issue is present in the latest release.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 😯

SSR + loading state (which causes the comp. in question to not render in one of the SSR rendering passes, more on that below) cause inconsistent ID in the className of a specific component that renders on the second SSR rendering pass but not on the first (because its rendering is conditioned to having the data available).

This causes the markup sent from the server to have a different className for this component, causing a visual inconsistency in when hydration happens:

SSRed component: ssredco

Hydrated component: hydratedco

The actual class available in the DOM is:

.PrivateSwitchBase-input-393 {
  top: 0;
  left: 0;
  width: 100%;
  cursor: inherit;
  height: 100%;
  margin: 0;
  opacity: 0;
  padding: 0;
  z-index: 1;
  position: absolute;
}

But because of the name mismatch, an inexistent class PrivateSwitchBase-input-411 is applied to the CheckBox input, and it’s not made invisible.

And I get the following warning from React:

Warning: Prop `className` did not match. Server: "PrivateSwitchBase-input-411" Client: "PrivateSwitchBase-input-393"

Expected Behavior 🤔

I’d expect the className to match and the component rendering to be the same in both the server and the client.

Steps to Reproduce 🕹

I have a TodoItem component:

import React from 'react';
import { 
  FormControlLabel,
  Checkbox
} from '@material-ui/core';

const TodoItem = (props) => {
  return (
    <FormControlLabel style={props.style} control={<Checkbox/>} label={props.title} />
  )
}

export default TodoItem;

And a Todos component (simplified version):

import React from 'react';
import  SortableTree, { getFlatDataFromTree } from '../lib/sortable-tree';
import { observer } from "mobx-react";
import { useQuery } from '../models/reactUtils';
import { Paper } from '@material-ui/core';

const Todos = observer((props) => {
  const {store, loading} = useQuery(store => store.fetchActiveTodoTree());

  return (
    <>
      <Paper style={{padding: '20px'}}>
        <SortableTree
          treeData={store.activeTodoTree.toJSON()}
          generateNodeProps={({node, path}) => ({
            title: (
              <TodoItem title={node.title} />
            ),
          })}
        />
      </Paper>
  )
});
  1. I load the page that renders the Todos component. This component loads some data using mst-gql and passes over to the SortableTree component;

  2. When running from the server, I use this function from mst-gql to wait for the data promises to be resolved and finally get the HTML to be sent back. Note that the component tree needs to be rendered twice, the first time to trigger any data fetching promises, then these promises are resolved, and finally, the last pass is done to render the tree with the data already available.

  3. After the markup from the server is sent to the client, then React.hydrate takes place. That’s when the component in question is then rendered with the visible input because of the inexistent CSS class.

I’m convinced the problem happens because of point 2 above. The first time the Todos component is rendered, the store.activeTodoTree is not yet available, so the SortableTree doesn’t render anything, hence the TodoItem component that’s supposed to be used inline by the SortableTree as its tree nodes (refer to the screenshots above) is not rendered the first time (but everything else is). I don’t know exactly how the className ID suffix generation logic works, but because of this, the suffix for the PrivateSwitchBase-input class (used for MUI’s CheckBox component’s internal checkbox input) has a mismatch of IDs between the server and the client, causing the visual glitch I’ve shown in the screenshots above.

One interesting thing though, is that the child nodes of the Foobar node, all render as expected even after hydration, as you may see below: wow.

You can see that the checkbox input for these nodes are hidden, which means the CSS class was correctly applied. I have no idea why that only happens to the root node though.

If I add a dummy <TodoItem/> that is always rendered in all SSR rendering passes, like this:

import  SortableTree, { getFlatDataFromTree } from '../lib/sortable-tree';
import { observer } from "mobx-react";
import { useQuery } from '../models/reactUtils';
import { Paper } from '@material-ui/core';

const Todos = observer((props) => {
  const {store, loading} = useQuery(store => store.fetchActiveTodoTree());

  return (
    <>
      <TodoItem title="I am here so that my className ID matches :("/> 
      <Paper style={{padding: '20px'}}>
        <SortableTree
          treeData={store.activeTodoTree.toJSON()}
          generateNodeProps={({node, path}) => ({
            title: (
              <TodoItem title={node.title} />
            ),
          })}
        />
      </Paper>
  )
});

Then the issue goes away and everything is rendered perfectly both from the server and upon hydrating in the client. This confirms the theory that the mismatch happens because in the first SSR rendering pass the <TodoItem> component is not rendered (as part of the SortableTree).

How am I supposed to deal with this? Is this a bug or am I missing something?

Thanks in advance and let me know if you need more information from my side!

Context 🔦

I’m just trying to get my SSRed component to render the same both in the server and the client, without visual glitches upon hydrating because of mismatched class names… 😃

Your Environment 🌎

"@material-ui/core": "^4.9.10",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.49",
"mobx-react": "^6.1.8",
"mobx-state-tree": "^3.15.0",
"mst-gql": "^0.7.1"
"react": "^16.10.2",
"react-dnd": "7.3.0",
"react-dnd-html5-backend": "7.0.1",
"react-dom": "^16.10.2",
"react-helmet": "^5.2.1",
"react-helmet-async": "^1.0.2",

Browser: Chrome and Firefox, latest versions.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
fullofcaffeinecommented, Apr 27, 2020

Happens it wasn’t really related to material-ui (or mst-gql). If anyone is curious, you may check my answer here: https://stackoverflow.com/questions/61277705/loading-state-second-ssr-rendering-pass-causing-a-client-side-rendering-glitch.

1reaction
oliviertassinaricommented, Apr 20, 2020

@fullofcaffeine You make sure it’s present or not (NoSsr can help) in both passes.

Read more comments on GitHub >

github_iconTop Results From Across the Web

[SSR] Mismatch in className suffix when SSRing a ... - GitHub
[SSR] Mismatch in className suffix when SSRing a component when the first rendering pass doesn't render the component #20604.
Read more >
Loading state + second SSR rendering pass causing a client ...
The first time the Todos component is rendered, the store.activeTodoTree data is not yet available, so the SortableTree component doesn't render ...
Read more >
Handling the React server hydration mismatch error
How to resolve the server mismatch error when hydrating a shared React component that can be used in client-side only or server-side rendered...
Read more >
Localized Server-Side Rendering with React | Phrase
We'll build a server-side rendered app with React and Razzle, ... Router's standard <Switch> component, which renders only the first <Route> ...
Read more >
Loading State + Second Ssr Rendering Pass Causing A Client-Side ...
[SSR] Mismatch in className suffix when SSRing a component when the first rendering pass doesn't render the component #20604. Closed.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found