Redux initial state is has values, but it shows null in redux dev-tool. Why?

130 Views Asked by At

I've been getting this error where I have set the initialState of redux with a value, but while debugging it shows its value is null. I'm using Redux Dev-tools to check the values. And also while adding data inside the initialState it throws an error Uncaught TypeError: Cannot read properties of null (reading 'push')

Here's my code:

Store.js

import { configureStore } from "@reduxjs/toolkit";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import authReducer from "../feature/auth/auth.js";

const persistConfig = {
  key: "root",
  storage,
};

const persistedReducer = persistReducer(persistConfig, authReducer);

export const store = configureStore({
  reducer: {
    auth: persistedReducer, // Use the persisted reducer here
  },
});

export const persistor = persistStore(store);

Feature.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  cookies: [{value: 'test'}]
};

const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    setCookies: (state, action) => {
      const data = {
        value: action.payload.value
      }
      state.cookies.push(data)
    },
    removeCookies: (state) => {
      state.cookies = [];
    }
  },
});

export const { setCookies, removeCookies } = authSlice.actions;
export default authSlice.reducer;

And this is how I'm trying to add cookies:

import { useDispatch } from "react-redux";
import { setCookies } from "../feature/auth/auth";

function About() {
  const dispatch = useDispatch()

  const handleCookies = () => {
    dispatch(setCookies({value: 'test'}))
  }

  return (
    <div>
      <h1>About</h1>
      <p>This is the about page</p>
      <button onClick={handleCookies}>Add Cookeis</button>
    </div>
  )
}

export default About;

Redux

2

There are 2 best solutions below

0
On BEST ANSWER

It appears that cookies is null due to your local storage having {cookies: null}. Possibly from code earlier in development?

Merging involves saving the persisted state back in the Redux store. When our app launches, our initial state is set. Shortly after, Redux Persist retrieves our persisted state from storage, then overrides any initial state. This process works automatically.

https://blog.logrocket.com/persist-state-redux-persist-redux-toolkit-react/

A possible solution would be to delete your local storage, allowing for the cookies value to properly initialize as an array, instead of null. This would also prevent the Uncaught TypeError: Cannot read properties of null (reading 'push') from being thrown since it would no longer be calling .push() on null

0
On

It appears you have persisted some previous state where the state.cookies value was a null value. When the app loads, the Redux store is rehydrated and the persisted null value is restored back into state.

You have a couple options when the state shape changes like this:

  1. Delete the persisted state from localStoreage and reload the page. This allows the app to mount/load and since there is no persisted state to rehydrate the store, the reducer's will use their initial state values, e.g. authSlice will be initialized to { cookies: [{value: 'test'}] }.

  2. Migrate your old state shapes to your new state shapes. See Redux-Persist Migrations for details.

    Basic example:

    import { configureStore } from "@reduxjs/toolkit";
    import { createMigrate, persistReducer, persistStore } from 'redux-persist';
    import storage from "redux-persist/lib/storage";
    import authReducer from "../feature/auth/auth.js";
    
    const migrations = {
      // Migrate state with invalid cookies value to one that is an array
      0: (state) => {
        return {
          ...state,
          auth: {
            ...state.auth,
            cookies: [],
          },
        };
      },
    };
    
    const persistConfig = {
      key: "root",
      version: 0, // <-- version the persisted state
      storage,
      migrate: createMigrate(migrations, { debug: false }), // <-- migrate state
    };
    
    const persistedReducer = persistReducer(persistConfig, authReducer);
    
    export const store = configureStore({
      reducer: {
        auth: persistedReducer,
      },
    });
    
    export const persistor = persistStore(store);
    

If you are still just doing local development and there aren't versions of your app out in the wild with the invalid state, then option 1 is fine, since it really only affects you. However, if once you have users out there using your app, you'll want to use option 2 so you can gracefully migrate their versions of state.