React router, how to restore state after browser back button?

nino.porcino picture nino.porcino · Nov 26, 2017 · Viewed 18.4k times · Source

I'm trying to figure out how to make my React SPA app maintain state when the user navigates with the back/forward browser buttons. E.g. the user is filling a form, then he moves back and forward and the form is automatically restored.

I reviewed many JavaScript routers around but none seem to address this issue properly... (or even at all).

Currently I'm using React Router 4 and this is the pattern I'm adopting:

  1. when programmatically leaving the page, I first save current page's state with history.replace(this.state); then I move out of the page with history.push(newLocation)
  2. when a page component is created (componentWillMount) I check for this.props.location.state !== undefined and if so, I restore it with this.setState(this.props.location.state)

My question is: is the above pattern correct? suggested? Is there any better and widely adopted way?

Answer

nino.porcino picture nino.porcino · Dec 9, 2017

after a while I found a reasonable workaround:

  • in react, after every this.setState() I keep state synchronized with history using window.history.replaceState({ key: history.location.key, state: this.state})
  • when a "page" component is mounted or refreshed (willMount and willReceiveProps) I check for state in props.params.location.state: if there is one, I do restore it; if there is none I create a fresh new state.
  • when navigating on the same page, I do not use routes, I just use this.setState and window.history.pushState
  • when navigating outside of the page, I just use routes and avoid to pass anything in the state

This solution seems to work nicely, the only minor cons are:

  • state must be serializable
  • this.setState is a pitfall because it's asynchronous, you cannot use this.state after it, unless you do trickery.
  • initial empty state must be provided by a function to be used during the restore or init phase, it can't just stay in the constructor() of the Component
  • in Firefox, automatic scroll restoration after the back button works randomly, don't know why, but Chrome and Edge are ok.

Overall I have written a PageComponent that extends Component that does all the init/restoration work; it also overrides this.setState to make it syncs automatically with history and avoids the asynchronous annoyances.