Trees4theForest
Trees4theForest

Reputation: 1386

How can I set a global context in Next.js without getting a "Text content did not match" error?

I have a next.js App that needs to pull SESSION data in to a global context. Currently I have:

// _app.js

const App = ({ Component, pageProps }) => {

const start = typeof window !== 'undefined' ? window.sessionStorage.getItem('start') : 0;

  return (
    <ElapsedContext.Provider value={start}>
      <Component {...pageProps} />
    </ElapsedContext.Provider>
  )
};

export default App;

and I'm consuming in my Component like so:

function MyComponent(props) {
  const start = useContext(ElapsedContext);
  return (
    // something using start;
  );
}

However, when I consume that context in a component, the Component renders on the page as expected, but I get Warning: Text content did not match. Server: "0" Client: "5.883333333333345"

Which I think it because it initially passes the 0, then pulls the number from SESSION storage after the window loads.

  1. How can I fix this warning?
  2. Can I safely ignore this warning?

I've tried using useEffect in the _app.js file (which fires after window is loaded) but the initial state is then not available for my component to build what it needs built on initial render...

Upvotes: 4

Views: 9291

Answers (1)

james
james

Reputation: 5226

Next.js renders pages either during build time or it server renders the page, which means window and therefore sessionStorage is not available as this runs in a Node.js environment.

If you ignore this warning, React will perform a re-render after the page loads. The great thing about server rendering React is that by the time the page loads React doesn't have to perform a re-render, so when this warning shows up you want to avoid it.

Although, because sessionStorage isn't available until the page is rendered in the browser, you'll have to wait to fill your Context until then.

Therefore, one way to avoid this error for your case would be to do something like:

// context.js
export const ElapsedContext = React.createContext(0);

export const ElapsedProvider = ({ children }) => {
  const [state, setState] = React.useState(0);

  React.useEffect(() => {
    // on client side mount, set starting value
    setState(window.sessionStorage.getItem('start'))
  }, [])

  return (
    <ElapsedContext.Provider
      value={state}
    >
      {children}
    </ElapsedContext.Provider>
  );
};


// pages/_app.js
export default function MyApp({ Component, pageProps }) {
  return <ElapsedProvider><Component {...pageProps} /></ElapsedProvider>
}

// pages/index.js
export default function MyPage() {
  const start = React.useContext(ElapsedContext);
  if (start === 0) {
    return <Loading />
  }

  return <MyComponentThatUsesElapsed />
}

Upvotes: 3

Related Questions