Search code examples
javascriptreactjsurlbrowser-history

How to reconcile React's setState with using the URL as the single source of truth?


In React's documentation they recommend generally abstaining from the use of this.forceUpdate, instead recommending to use this.setState whenever possible.

In my application I have a component that contains a slide-out pane. It has nested elements and clicking each item updates the URL in a different way. For example, I could navigate to the following URLs by clicking around in it:

site.com/posts/create
site.com/posts/read
site.com/posts/4895734
site.com/posts/5465462
site.com/messages/43455
site.com/messages/create

Clicking each of these items should update the component's view and display it differently based on the URL. However, just calling window.history.pushState to update the URL does not cause React to update (you need to call setState or forceUpdate for that).

As a result, I am unsure of how to proceed. I rather not duplicate the "source of truth" and mirror the URL in React's state variables, because then there is conflict over whether or not the URL should be used to determine the state versus the component's state variables. It seems better to have the URL be the single source of truth.

However, if this is the case, then every time I change the URL I would have to do this.forceUpdate or some no-op call like this.setState({ asdf: 5 }). This would certainly work, but React's documentation specifically warns about calling forceUpdate all over the place in the application.

Is there a better way?


Solution

  • I'm not sure what routing configuration you have, but are you using the history package? https://reactrouter.com/core/api/history

    https://www.npmjs.com/package/history

    Example:

    https://codesandbox.io/s/vigilant-dhawan-qf6ke?file=/src/App.js

    That should mount/rerender on url changes, as well as give the ability to pass state through the router.

    Edit:

    Simplified version

    https://codesandbox.io/s/trusting-cookies-pyhq6?file=/src/Router.js