React router, how to restore state after browser back button?
after a while I found a reasonable workaround:
- in react, after every
this.setState()
I keep state synchronized with history usingwindow.history.replaceState({ key: history.location.key, state: this.state})
- when a "page" component is mounted or refreshed (
willMount
andwillReceiveProps
) I check for state inprops.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
andwindow.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 usethis.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 theComponent
- 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.
Use getDerivedStateFromProps
and check if there has been a change in value of props.location.key
to restore states only when the user navigates with browser buttons.
static getDerivedStateFromProps(props,states){ if ((typeof props.location !== 'undefined') && (typeof props.location.state !== 'undefined') && (states.location_key !== props.location.key)) { let newState = props.location.state; newState.location_key = props.location.key; return newState; } }
You can also use the session storage instead of state. Just replace the useState()
function and use the useSessionStorage()
.
For example:
const [myState, setMyState] = useSessionStorage("state_key", "initial_value")const handleSomeChangeOnState = (event: any) => { setMyState("new_value")}