2

I'm using react-static to generate a static website. Using useLayoutEffect from the new hook API, I get this warning during the static rendering phase (same API as server-side rendering) :

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format.
  This will lead to a mismatch between the initial, non-hydrated UI and th  e intended UI.
  To avoid this, useLayoutEffect should only be used in components that render exclusively on the client.

Of course this makes sense. But what's the good option to get rid of this warning when one's sure there won't be any mismatch ?

In my layout effect I'm only adding a bit of CSS to the body tag, so there won't be any mismatch during the hydration phase on the client (since body isn't React's business).

React strongly forbids using conditional hooks, but in that very specific case, wouldn't it make sense to do something like :

if(typeof window !== 'undefined')
  useLayoutEffect(() => {
      document.body.style.overflowY = loading ? 'hidden' : 'visible'
    },
    [loading]
  )

What's the proper way ?

ostrebler
  • 940
  • 9
  • 32

2 Answers2

0

I think that you should use a Context to pass the value "hidden/visible" to other components. In this case, the component that renders a wrapper of all the page.

useEffect(() => { 
  fnSetContextValue(isLoading ? 'hidden' : 'visible') 
}, [isLoading]);

You also can try with the requestAnimationFrame function instead of a Context:

useEffect(() => { 
  window.requestAnimationFrame(() => {
      document.body.style.overflowY = loading ? 'hidden' : 'visible';
  });
}, [isLoading]);
David Táboas
  • 800
  • 6
  • 8
  • 1
    Using `useEffect` instead of `useLayoutEffect` indeed doesn't trigger the warning, but then I lose the synchronous aspect of `useLayoutEffect`. – ostrebler Mar 05 '19 at 10:43
  • Oh, I wrote `useEffect` in my post, true. Corrected. I meant `useLayoutEffect`. – ostrebler Mar 05 '19 at 10:58
0

Alright, so here is the not-so-dirty solution I came up with. Instead of implementing the naive solution, ie. a conditional hook :

const Layout = () => {
  const [loading, setLoading] = useState()

  if(typeof window !== 'undefined')
    useLayoutEffect(() => {
      document.body.style.overflowY = loading ? 'hidden' : 'visible'
    }, [loading])

  return ( ... )
}

export default Layout

which feels dirty, anti-pattern, semantically wrong and useless in many cases (why checking window at each render ?), I just put the condition outside of the component :

const LayoutView = ({ loading, setLoading }) => ( ... )

const Layout = (typeof window === 'undefined') ? (
  () => {
    const [loading, setLoading] = useState()
    return <LayoutView loading={loading} setLoading={setLoading}/>
  }
): (
  () => {
    const [loading, setLoading] = useState()
    useLayoutEffect(() => {
      document.body.style.overflowY = loading ? 'hidden' : 'visible'
    }, [loading])
    return <LayoutView loading={loading} setLoading={setLoading}/>
  }
)

export default Layout

Take care though, this only works because my layout effect doesn't affect React's part of the DOM, which was the whole point of the warning.

ostrebler
  • 940
  • 9
  • 32