How to save Redux state properly after refreshing the page?
Asked Answered
Y

4

7

Code for request:

export class LogInContainer extends Component {

  submit = (values) => {
    const { mutate } = this.props;
    mutate({
      variables: {
        email: values.email,
        password: values.password,
      },
    })
      .then((res) => {
        const token = res.data.login.token;
        localStorage.setItem("token", token);
        const user = res.data.login.user;
        const { logIn } = this.props;
        logIn(user);
        this.props.history.push("/pages/processes");
      })
      .catch((error) => {
        const errorMessage = error.message;
        const { logInError } = this.props;
        logInError(errorMessage);
      });
  };

  render() {
    const { error, logInCleanError } = this.props;
    return (
      <LogIn
        onSubmit={this.submit}
        errorMessage={error}
        logInCleanError={logInCleanError}
      />
    );
  }
}

export default compose(
  graphql(LOG_IN),
  withRouter,
  connect(({ errors }) => ({ error: errors.log_in_error }), {
    logInError,
    logInCleanError,
    logIn,
  })
)(LogInContainer);

Code for reducer:

const initialState = {
  user: {
    id: "",
    firstName: "",
    secondName: "",
    email: "",
    isAuth: localStorage.getItem('token') ? true : false,
  },
};

export const userReducer = (state = initialState, { type, user }) => {
  switch (type) {
    case LOG_IN:
      return {
        ...state,
        user: {
          id: user.id,
          firstName: user.firstName,
          secondName: user.secondName,
          email: user.email,
          isAuth: true,
        },
      };
    case CURRENT_USER:
      return {
        ...state,
        user: {
          id: user.id,
          firstName: user.firstName,
          secondName: user.secondName,
          email: user.email,
          isAuth: true,
        },
      };
    case LOG_OUT:
      return {
        ...state,
        user: {
          id: "",
          firstName: "",
          secondName: "",
          email: "",
          isAuth: false,
        },
      };
    default:
      return state;
  }
};

I save JWT and put it in the headers for every request like:

const client = new ApolloClient({
  uri: "http://localhost:4000/api",
  request: (operation) => {
    const token = localStorage.getItem("token");
    operation.setContext({
      headers: {
        authorization: token ? `Bearer ${token}` : "",
      },
    });
  },
}); 

And I protect my routers like:

const App = ({ authorized }) => {
  return (
    <Switch>
      {!authorized ? (
        <Route path="/auth">
          <Auth />
        </Route>
      ) : (
        <Route path="/pages">
          <Pages />
        </Route>
      )}
      <Redirect from="/" to="/auth/login" />
    </Switch>
  );
};

export default App;

After refreshing the page being logged in, I don't lose JWT and stay in the system, but I lose redux state with user's firstname, secondname and etc. which I need to show on the page. So what is the proper way to save redux state in that case without plugins like redux-persist or something else

Year answered 20/5, 2020 at 19:7 Comment(1)
Look at redux-persistVertumnus
D
2

As Dan Abramov suggests you should use the local storage in the store's subscribe method:

store.subscribe(() => {
  // persist your state
})

Before creating the store, read those persisted parts:

const persistedState = // ...
const store = createStore(reducer, persistedState)

If you use combineReducers() you’ll notice that reducers that haven’t received the state will “boot up” as normal using their default state argument value. This can be pretty handy.

It is advisable that you debounce your subscriber so you don’t write to localStorage too fast, or you’ll have performance problems.

Finally, you can create a middleware that encapsulates that as an alternative, but I’d start with a subscriber because it’s a simpler solution and does the job well.

Dc answered 20/5, 2020 at 19:33 Comment(0)
E
2

🎈 Shortly

Make this:

const initialState = {
  user: {
    id: localStorage.getItem('id') || '',
    firstName: localStorage.getItem('firstName') || '',
    secondName: localStorage.getItem('secondName') || '',
    email: localStorage.getItem('email') || '',
    isAuth: localStorage.getItem('token') ? true : false,
  },
}

📿 A little bit complicated but extendable way

Create a middleware which will serve everything related to application persisting state. There you can save data to localStorage, cookie or IndexedDB. The middleware, let's call it LocalStoreMiddleware will listen same actions what your redux reducers do, when an action will be dispatched no matter where and by whom (Component, Saga, Socket) every middleware will do their responsible job, reducers will update redux store, LocalStoreMiddleware will save data to localStorage, cookie, indexDB and whatever.

The advantages

  • no tight dependencies
  • easy to extend
  • one place for one domain business logic
  • you make some elves happy from Narnia world

Create a file where you will store your functions related to persisting data

// local-store-resolvers.js
import {on} from './local-store-middleware.js'

function saveToken(action, state) {
  localStorage.setItem('token', JSON.stringify(state.token));
}

function saveUsername(action) {
  localStorage.setItem('username', JSON.stringify(action.username));
}

export default [
  on(AUTHORIZATION, saveToken),
  on(USERNAME_CHANGED, saveUsername),
]

Middleware factory

// local-store-middleware.js
export const on = (actionType, resolver) => (action, state) => (
  actionType === action.type && resolver(action, state)
)

const createLocalStoreMiddleware = resolvers => store => next => action => {
  resolvers.forEach(
    resolver => resolver(action, store.getState())
  )
  return next(action)
}

export default createLocalStoreMiddleware

Redux store

// src/store/index.js - your main file where you create redux store
import createLocalStoreMiddleware from './local-store-middleware.js'
import resolvers from './local-store-resolvers.js'

const localStore = createLocalStoreMiddleware(resolvers)

const store = createStore(
  combineReducers({users, someOtherReducer}),
  applyMiddleware(localStore), // if you have more middlewares use compose
)

export default store
Eliason answered 24/5, 2020 at 22:26 Comment(0)
P
0

use localStorage:

localStorage.setItem('token', token);

And on load of app check if token is set, if it is, get info from localStorage

localStorage.getItem('token');

You can also save user info:

localStorage.setItem('user', JSON.stringify(userInfo));

and get it with JSON.parse.

Padegs answered 20/5, 2020 at 19:15 Comment(0)
R
0

A neat and clean solution to save the redux state even after refresh is Redux persist.

It works like a charm.

https://www.npmjs.com/package/redux-persist

Rensselaerite answered 20/5, 2020 at 19:33 Comment(1)
project is not maintained anymore :(Piny

© 2022 - 2024 — McMap. All rights reserved.