'How to use redux toolkit's preloadedState and createSlice together properly?

I am trying to migrate to redux toolkit, but just encountered an issue.

Here's an example of simple counter slice.

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

const initialState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
  },
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;

And here is a store which is created with configureStore.

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./slice";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    // later, many other reducers will be added here.
  },
});

There's yet any problem.

But if I introduce preloadedState, there exists a problem.

const store = configureStore({
    reducer: counterReducer,
    preloadedState: {
      counter: {
         value: 10
      }
    },
  });

If I log store's state like below, it is logged as expected.

   // without using preloadedState, it results to {counter: {value: 0}}
   console.log(store.getState())
   // with using preloadedState, it results to {counter: {value: 10}}
   console.log(store.getState())

But there arises a problem when using slice reducer, since slice reducer uses its own state.

   ...
     reducers: {
       increment: (state) => {
         state.value += 1;  // not work anymore if using preloadedState. we have to use state.counter.value instead.
       },
       decrement: (state) => {
         state.value -= 1;  // not work anymore if using preloadedState. we have to use state.counter.value instead.
       },
     },
   ...

instead we have to use,

   ...
     reducers: {
       increment: (state) => {
         state.counter.value += 1; 
       },
       decrement: (state) => {
         state.counter.value -= 1;
       },
     },
   ...

So the question is, do we have to add conditional statement, seperate the logic inside the slice reducer depending on whether we are using preloadedState or not?

It would be nice if slice reducer uses preloadedState instead of using its own state, whenever preloadedState is provided. Are there any better approaches?

Thank you.



Solution 1:[1]

If you look at your Redux Devtools browser extension, you will notice that - since you only have one reducer and you passed that one in as the root reducer, your state has a shape of

{
  value: 10
},

your normal state at this point just does not have a subkey for your counter reducer, since your whole state is the counter reducer.

If you want your state to have a reasonable structure (and in the future be able to use more than one reducer), you have to add the reducer key as an object:

const store = configureStore({
    reducer: {
      counter: counterReducer,
    },
    preloadedState: {
      counter: {
         value: 10
      }
    },
  });

As you will notice in the devtools, now the structure of your state is what you expect - and also the preloaded state will match that.

But keep in mind, that from now on in selectors you will have to useSelector(state => state.counter.value)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 phry