[javascript] How to maintain state after a page refresh in React.js?

Lets say I have code that sets state for a select box chosen on the previous page:

this.setState({selectedOption: 5});

Is there any way to have this.state.selectedOption populated with 5 after a page refresh?

Are there callbacks that I can use to save this in localStorage and then do one large setState or is there a standard way of doing something like this?

This question is related to javascript reactjs

The answer is


You can "persist" the state using local storage as Omar Suggest, but it should be done once the state has been set. For that you need to pass a callback to the setState function and you need to serialize and deserialize the objects put into local storage.

constructor(props) {
  super(props);
  this.state = {
    allProjects: JSON.parse(localStorage.getItem('allProjects')) || []
  }
}


addProject = (newProject) => {
  ...

  this.setState({
    allProjects: this.state.allProjects.concat(newProject)
  },() => {
    localStorage.setItem('allProjects', JSON.stringify(this.state.allProjects))
  });
}

So my solution was to also set localStorage when setting my state and then get the value from localStorage again inside of the getInitialState callback like so:

getInitialState: function() {
    var selectedOption = localStorage.getItem( 'SelectedOption' ) || 1;

    return {
        selectedOption: selectedOption
    };
},

setSelectedOption: function( option ) {
    localStorage.setItem( 'SelectedOption', option );
    this.setState( { selectedOption: option } );
}

I'm not sure if this can be considered an Anti-Pattern but it works unless there is a better solution.


I may be late but actual code for react-create-app for react > 16 ver. After each change state is saved in sessionStorage (not localStorage) and is crypted via crypto-js. On refresh (when user demands refresh of the page by clicking refresh button) state is loaded from the storage. I also recommend not to use sourceMaps in build to avoid readablility of the key phrases.

my index.js

import React from "react";
import ReactDOM from "react-dom";
import './index.css';
import App from './containers/App';
import * as serviceWorker from './serviceWorker';
import {createStore} from "redux";
import {Provider} from "react-redux"
import {BrowserRouter} from "react-router-dom";
import rootReducer from "./reducers/rootReducer";
import CryptoJS from 'crypto-js';

const key = CryptoJS.enc.Utf8.parse("someRandomText_encryptionPhase");
const iv = CryptoJS.enc.Utf8.parse("someRandomIV");
const persistedState = loadFromSessionStorage();

let store = createStore(rootReducer, persistedState,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

function loadFromSessionStorage() {
    try {
        const serializedState = sessionStorage.getItem('state');
        if (serializedState === null) {
            return undefined;
        }
        const decrypted = CryptoJS.AES.decrypt(serializedState, key, {iv: iv}).toString(CryptoJS.enc.Utf8);
        return JSON.parse(decrypted);
    } catch {
        return undefined;
    }
}

function saveToSessionStorage(state) {
        try {
            const serializedState = JSON.stringify(state);
            const encrypted = CryptoJS.AES.encrypt(serializedState, key, {iv: iv});
            sessionStorage.setItem('state', encrypted)
        } catch (e) {
            console.log(e)
        }
}

ReactDOM.render(
    <BrowserRouter>
        <Provider store={store}>
            <App/>
        </Provider>
    </BrowserRouter>,
    document.getElementById('root')
);

store.subscribe(() => saveToSessionStorage(store.getState()));

serviceWorker.unregister();

I consider state to be for view only information and data that should persist beyond the view state is better stored as props. URL params are useful when you want to be able to link to a page or share the URL deep in to the app but otherwise clutter the address bar.

Take a look at Redux-Persist (if you're using redux) https://github.com/rt2zz/redux-persist


Load state from localStorage if exist:

constructor(props) {
    super(props);           
    this.state = JSON.parse(localStorage.getItem('state'))
        ? JSON.parse(localStorage.getItem('state'))
        : initialState

override this.setState to automatically save state after each update :

const orginial = this.setState;     
this.setState = function() {
    let arguments0 = arguments[0];
    let arguments1 = () => (arguments[1], localStorage.setItem('state', JSON.stringify(this.state)));
    orginial.bind(this)(arguments0, arguments1);
};

We have an application that allows the user to set "parameters" in the page. What we do is set those params on the URL, using React Router (in conjunction with History) and a library that URI-encodes JavaScript objects into a format that can be used as your query string.

When the user selects an option, we can push the value of that onto the current route with:

history.push({pathname: 'path/', search: '?' + Qs.stringify(params)});

pathname can be the current path. In your case params would look something like:

{
  selectedOption: 5
}

Then at the top level of the React tree, React Router will update the props of that component with a prop of location.search which is the encoded value we set earlier, so there will be something in componentWillReceiveProps like:

params = Qs.parse(nextProps.location.search.substring(1));
this.setState({selectedOption: params.selectedOption});

Then that component and its children will re-render with the updated setting. As the information is on the URL it can be bookmarked (or emailed around - this was our use case) and a refresh will leave the app in the same state. This has been working really well for our application.

React Router: https://github.com/reactjs/react-router

History: https://github.com/ReactTraining/history

The query string library: https://github.com/ljharb/qs