Search code examples
reactjsreduxreact-reduxreact-router-v4react-router-dom

React Redux state is lost at page refresh


in my react app I have 3 components. from the 1st component by a button, I use Link to go to the 2nd component. at the 2nd I create a state (redux store), manipulate it and when manipulation is finished by submitting button I redirect to the 3rd component. at the 3rd component, I also see the state (by redux chrome tool) but here when I refresh the page (the webpack thing when a code is changed and saved), I lose the sate and get an empty Object.

this is the structure of my app.

index.js

const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunk))
);

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

App.js

const App = ({ location }) => (
  <Container>
    <Route location={location} path="/" exact component={HomePage} />
    <Route location={location} path="/GameInit" exact component={GameInit} />
    <Route location={location} path="/Battle" exact component={Battle} />
  </Container>
);

App.propTypes = {
  location: PropTypes.shape({
    pathname: PropTypes.string.isRequired
  }).isRequired
};

export default App;

Here at HomePage, I have a button that links to GameInit

const HomePage = () => (<Button color="blue" as={Link} to="/GameInit">START GAME</Button>);
export default HomePage;

and the GameInit Page looks like

class GameInit extends Component {
  componentWillMount() {
    const { createNewPlayer} = this.props;
    createNewPlayer();
  }
/* state manipulation till it gets valid */
 palyerIsReady()=>{ history.push("/Battle");}
 
export default connect(mapStateToProps,{ createNewPlayer})(GameInit);

and finally Battle component where I first see the state but lose on refresh

class Battle extends Component {
  render() {return (/*some stuff*/);}}

Battle.propTypes = {
  players: PropTypes.object.isRequired // eslint-disable-line
};
const mapStateToProps = state => ({
  players: state.players
});
export default connect(mapStateToProps,null)(Battle);

Solution

  • You can easily persist it in the localstorage. Check the example below.

    const loadState = () => {
      try {
        const serializedState = localStorage.getItem('state');
        if(serializedState === null) {
          return undefined;
        }
        return JSON.parse(serializedState);
      } catch (e) {
        return undefined;
      }
    };
    
    const saveState = (state) => {
      try {
        const serializedState = JSON.stringify(state);
        localStorage.setItem('state', serializedState);
      } catch (e) {
        // Ignore write errors;
      }
    };
    
    const peristedState = loadState();
    
    store.subscribe(() => {
      saveState(store.getState());
    });
    
    const store = createStore(
      persistedState,
      // Others reducers...
    );
    
    render(
      <Provider store={store}>
        <App/>
      </Provider>,
      document.getElementById('root');
    );
    

    Serialization is an expensive operation. You should use a throttle function (like the one implemented by lodash) to limit the number of saves.

    Eg:

    import throttle from 'lodash/throttle';
    
    store.subscribe(throttle(() => {
      saveState(store.getState());
    }, 1000));