Local storage using redux toolkit
Asked Answered
C

5

18

I would like to keep my isAuthenticated state in local storage, so after refreshing the page, the user will be logged in. I tried straightforward, to set it ti true/false in localStorage and to set initial value of my state in redux to this value, but it always sets it to true.

Here's my redux store

import { createSlice, configureStore } from '@reduxjs/toolkit';

//MOVEMENTS (doesn't work yet)
const initialMovementsState = {
  movements: [],
};

const movementsSlice = createSlice({
  name: 'movements',
  initialState: initialMovementsState,
  reducers: {
    add(state) {
      //nothing yet
    },
    decrement(state) {
      //nothing yet
    },
  },
});

//LOGGING IN/OUT
const initialAuthState = {
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: 'auth',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

//STORE CONFIGURATION

const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
});

export const movementsActions = movementsSlice.actions;
export const authActions = authSlice.actions;

export default store;

All answers I found are with redux only, not with redux toolkit and I'm kinda fresh to redux, so I'm lost.

Calmas answered 17/7, 2021 at 13:41 Comment(1)
Hey, I'm coming here with an update. Actually nobody in the comments recommended me redux persist, which would totally satisfy me for this issue :D The most up-voted answer is a gem though, so I leave the question for othersCalmas
E
46

Update October 2022: You can also use redux-toolkit's createListenerMiddleware in versions 1.8 and up, as explained in this answer.


Changing localStorage is a side-effect so you don't want to do it in your reducer. A reducer should always be free of side-effects. One way to handle this is with a custom middleware.

Writing Middleware

Our middleware gets called after every action is dispatched. If the action is login or logout then we will change the localStorage value. Otherwise we do nothing. Either way we pass the action off to the next middleware in the chain with return next(action).

The only difference in the middleware between redux-toolkit and vanilla redux is how we detect the login and logout actions. With redux-toolkit the action creator functions include a helpful match() function that we can use rather than having to look at the type. We know that an action is a login action if login.match(action) is true. So our middleware might look like this:

const authMiddleware = (store) => (next) => (action) => {
  if (authActions.login.match(action)) {
    // Note: localStorage expects a string
    localStorage.setItem('isAuthenticated', 'true');
  } else if (authActions.logout.match(action)) {
    localStorage.setItem('isAuthenticated', 'false');
  }
  return next(action);
};

Applying Middleware

You will add the middleware to your store in the configureStore function. Redux-toolkit includes some middleware by default with enables thunk, immutability checks, and serializability checks. Right now you are not setting the middleware property on your store at all, so you are getting all of the defaults included. We want to make sure that we keep the defaults when we add our custom middleware.

The middleware property can be defined as a function which gets called with the redux-toolkit getDefaultMiddleware function. This allows you to set options for the default middleware, if you want to, while also adding our own. We will follow the docs example and write this:

const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
  // Note: you can include options in the argument of the getDefaultMiddleware function call.
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(authMiddleware)
});

Don't do this, as it will remove all default middleware

const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
  middleware: [authMiddleware]
});

Syncing State via Middleware

We could potentially streamline our middleware by matching all auth actions. We do that by using the String.prototype.startsWith() method on the action.type (similar to the examples in the addMatcher docs section which use .endswith()).

Here we find the next state by executing next(action) before we change localStorage. We set the localStorage value to the new state returned by the auth slice.

const authMiddleware = (store) => (next) => (action) => {
  const result = next(action);
  if ( action.type?.startsWith('auth/') ) {
    const authState = store.getState().auth;
    localStorage.setItem('auth', JSON.stringify(authState))
  }
  return result;
};

Or you can use the redux-persist package, which does that for you.

Evetteevey answered 17/7, 2021 at 14:33 Comment(1)
Awesome! Thanks for help. It didn't entirely solve my problem, but I was digging through docs and other stack overflow threads - it was successfully add things to my local storage, but I was struggling how to 'send' it back to my state. But it was huge help, thank you!Calmas
C
20

In the meantime I've written the logic for movements and wanted to keep all my state in the local storage. The answer of Linda Paiste was very helpful (and kudos for such a long and straightforward answer!), but I was struggling with sending my local storage back to my redux state. Here's the working solution:

import { createSlice, configureStore } from '@reduxjs/toolkit';
import dummyItems from '../helpers/dummyItems';

const initialMovementsState = {
  movements: dummyItems,
};

const movementsSlice = createSlice({
  name: 'movements',
  initialState: initialMovementsState,
  reducers: {
    add(state, action) {
      state.movements = [action.payload, ...state.movements];
    },
    delete(state, action) {
      const id = action.payload;
      state.movements = state.movements.filter(mov => mov.id !== id);
    },
  },
});

//AUTHORIZATION
const initialAuthState = {
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: 'auth',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

//MIDDLEWARE
const localStorageMiddleware = ({ getState }) => {
  return next => action => {
    const result = next(action);
    localStorage.setItem('applicationState', JSON.stringify(getState()));
    return result;
  };
};

const reHydrateStore = () => {
  if (localStorage.getItem('applicationState') !== null) {
    return JSON.parse(localStorage.getItem('applicationState')); // re-hydrate the store
  }
};

//STORE CONFIGURATION
const store = configureStore({
  reducer: {
    movements: movementsSlice.reducer,
    auth: authSlice.reducer,
  },
  preloadedState: reHydrateStore(),
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(localStorageMiddleware),
});

export const movementsActions = movementsSlice.actions;
export const authActions = authSlice.actions;

export default store;

Calmas answered 17/7, 2021 at 17:15 Comment(4)
localeStorage is not available at the time when calling reHydrateStore function. @AniaKowalskaAssistant
Of course it is, the above snippet is valid and working code as expectedCalmas
if you server render react app; localStorage will not be available, I tested it on nextjs.Assistant
Is it a case here though? The problem was solved with this code snippetCalmas
B
1

In Redux Toolkit, you can simply create a Middleware which save state data in storage, and you can create one more function for preloadedState to load data in to store when page reloads.

//MIDDLEWARE
const cartMiddleware = ({ getState }) => {
  return (next) => (action) => {
    const result = next(action);
    localStorage.setItem("cartData", JSON.stringify(getState()));
    return result;
  };
};

Get data from storage

const reHydrateStore = () => {
  if (localStorage.getItem("cartData") !== null) {
    return JSON.parse(localStorage.getItem("cartData")); // re-hydrate the store
  }
};

Now this middleware and preloadedState you need to include in store configuration to work as middleware for your state and load state respectively.

export const store = configureStore({
  reducer: cartSliceReducer,
  preloadedState: reHydrateStore(),
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(cartMiddleware),
});

enter image description here

Bogbean answered 7/2, 2024 at 12:31 Comment(0)
S
-2
import {configureStore} from '@reduxjs/toolkit';

import userReducer from '../userSlices';

const store =  configureStore({

    reducer:{
        user:userReducer,
    },
    preloadedState:loadFromLocalStorage()
});

function saveToLocalStorage(state){

    try{
      const serialState = JSON.stringify(state)
      localStorage.setItem("reduxStore",serialState)
    }catch(e){
      console.warn(e);
    }
  }
  
  function loadFromLocalStorage(){

    try{
      const serialisedState = localStorage.getItem("reduxStore");
      if(serialisedState === null) return undefined;
      return JSON.parse(serialisedState);
    }catch(e){
      console.warn(e);
      return undefined;
    }
  }

  store.subscribe(()=>saveToLocalStorage(store.getState()));
    
export default store;
Silvie answered 14/2, 2023 at 14:12 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Addison
P
-3

You must dispatch the login or logout action to actually changes your state in the redux store!

Privation answered 17/7, 2021 at 13:49 Comment(2)
Just simple use dispatch(login()) or dispatch(logout()). And you should change isAuthenticated in localStorage after you dispatch your action.Privation
I tried like this (like I mentioned in my question) and it doesn't work. It always sets it to true after refreshing.Calmas

© 2022 - 2025 — McMap. All rights reserved.