Skip to content

Instantly share code, notes, and snippets.

@bb
Created January 19, 2018 22:59
Show Gist options
  • Save bb/a6c9cdbdbe253f3a77c5212c827dc3b1 to your computer and use it in GitHub Desktop.
Save bb/a6c9cdbdbe253f3a77c5212c827dc3b1 to your computer and use it in GitHub Desktop.
SSR vs Client State: flicker or fail?
// this is pages/index.js of a fresh next.js app
import {observer} from 'mobx-react';
import {observable} from 'mobx';
const store = observable({ color: "#f00"})
const clientOnly = () => store.color = "#0f0";
if (typeof window != 'undefined') {
// clientOnly(); // too early: stays red + console error
// Promise.resolve().then(clientOnly); // too early: stays red + console error
setTimeout(clientOnly, 0); // too late: red visible
// window.requestAnimationFrame(clientOnly); // too late: red visible
}
export default observer(() =>
<div style={{background: store.color}}>
Background should be green without flickering to red on reload
</div>);
@bb
Copy link
Author

bb commented Jan 19, 2018

Try it:

mkdir -p fof/pages
cd fof
echo '{ "scripts": { "dev": "next" }}' > package.json
npm install --save next react react-dom mobx mobx-react
curl -L https://gist.github.com/bb/a6c9cdbdbe253f3a77c5212c827dc3b1/raw > pages/index.js
npm run dev

then http://localhost:3000/

@bb
Copy link
Author

bb commented Jan 20, 2018

Update and solution

If you really, really want to work around flickering and use client side, you can add an inline JS directly in the element. Inside of JSX this needs a little extra work with dangerouslySetInnerHTML. I also changed the example to use className, see below for explanation.

// this is pages/index.js of a fresh next.js app
import {observer} from 'mobx-react';
import {observable} from 'mobx';
import Head from 'next/head';

const store = observable({ color: "red"})
const clientOnly = () => store.color = "green";

if (typeof window != 'undefined') {
    clientOnly(); // works
    // Promise.resolve().then(clientOnly); // works
    // setTimeout(clientOnly, 0); // works, but warning (state update too late, see below)
    // window.requestAnimationFrame(clientOnly); // works, but warning (state update too late, see below)
}
export default observer(() =>
    <div id="foo" className={store.color} >
        <script dangerouslySetInnerHTML={{
            __html: `document.getElementById("foo").className = "green";`
        }}></script>
        <Head>
            <style>{".red{background: #f00} .green{background: #0f0}"}</style>
        </Head>
        Background should be green without flickering to red on reload
    </div>);

You may want to add the inline script tag only on SSR - that exercise is left for the reader (-;

Additional info for late client state updates and DOM inconsistencies

You need to make your existing DOM and the client state matching at the time react rehydrates. If client state is updated too late, you need to add suppressHydrationWarning={true} on the element you're modifying (i.e. div foo).

That may also be needed when you have an inconsistency in output, e.g. when using style instead of classname as in the initial example because of whitespace and color representation (#0f0 vs rgb(0, 255, 0) etc.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment