Łukasz Makuch

Łukasz Makuch

(React) Old state when the user navigates back

Clocks

Let's say you're developing a React app that consists of a component that has some side effects. Perhaps it does something very important when it's mounted. Perhaps it matters that on the initial render it's in the right state.

It all works well in the development mode, but the production build renders stale components when the user navigates back to the app after leaving it for another web page. In other words, when the user clicks the //back// button, the functions passed to useEffect don't run. After navigating back to the app, React doesn't notice the component being mounted. Moreover, it holds the old state that was there before the page was left! And it happens only in production builds! It never happens in the development mode. So, what exactly happens?

It's the doing of a mechanism known as bfcache (or Back-Forward Cache). It's a cache that sometimes holds your entire page in memory. It even includes the state of your JavaScript app. When the user navigates back, it's as if they never left the page. What's good is that it's a tremendous boost in performance. What's bad is that it may lead to some unexpected behavior and it happens just sometimes, not all the time.

You may expect your React app to be re-mounted when the user navigates back to it, but it won't happen. Instead, they will land on a page which is in the very same state it was the moment they left it.

What makes it even more tricky is that if you use Create React App you may never observe such behavior in the the development mode, because in this mode bfcache doesn't really work.

The good news is that you can register an event listener and be notified when the page is restored from the cache! Here's how you can use JavaScript to notice that the page is restored from the Back-Forward Cache:

window.addEventListener('pageshow', event => {
  if (event.persisted) {
    console.log("restored")
  }
});

Equipped with that information, you can decide to re-render your component or restore the desired state in some other way.

Alternatively, if you'd like to prevent the pages from being cached in the first place, for instance because they contain some sensitive information you would prefer not to leak, you may deliver the SPA with the following header:

cache-control: no-store

Resources: