Jplus2
Jplus2

Reputation: 2531

How do you define the initial state type in redux toolkit createSlice (And the types are different)

It is well documented here:

Defining the Initial State Type

However the problem arises when the initial state types could be of multiple different types.

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

// Type definitions
export const UNINITIALIZED = "UNINITIALIZED";
interface ICurrentCustomer {
  id: string;
  name: string;
}
interface ISetCurrentCustomerAction {
  type: string;
  payload: ICurrentCustomer;
}

// Initial state (This is intentional, null has special meaning here in my use case)
// This is the important part right here!!!!!!!!!!!!!!!!!!!!!!!
const initialState: ICurrentCustomer | typeof UNINITIALIZED | null = UNINITIALIZED;

const currentCustomerSlice = createSlice({
  name: "currentCustomer",
  initialState,
  reducers: {
    // I put any on state type here coz it will error
    setCurrentCustomer: (state: any, action: ISetCurrentCustomerAction) => {
      const { id, name } = action.payload;

      state.id = id;
      state.name = name;

      return state;
    },
  },
});

export const { setCurrentCustomer } = currentCustomerSlice.actions;

export default currentCustomerSlice.reducer;

The issue here is redux toolkit will set the type of the state in the store as string. Because obviously UNINITIALIZED is a string. This will produce an error and the console message is:

TypeError: Cannot create property 'id' on string 'UNINITIALIZED'

It is about type inference as mentioned here:

How to describe the shape of the state in createSlice

How could I make my code above work? Any help is appreciated.

Thanks in advance.

Upvotes: 1

Views: 5773

Answers (1)

coglialoro
coglialoro

Reputation: 773

You can use type cast with as to have createSlice infer the type correctly like this:

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

// Type definitions
export const UNINITIALIZED = "UNINITIALIZED";
interface ICurrentCustomer {
  id: string;
  name: string;
}
interface ISetCurrentCustomerAction {
  type: string;
  payload: ICurrentCustomer;
}

// Define a type for your state
type State = ICurrentCustomer | typeof UNINITIALIZED | null;

// Define your initial state
const initialState: State  = UNINITIALIZED;

const currentCustomerSlice = createSlice({
  name: "currentCustomer",
  // Do a type cast here, this will allow `createSlice` to infer the correct type
  initialState: initialState as State,
  reducers: {
    // You can use State type here now
    setCurrentCustomer: (state: State, action: ISetCurrentCustomerAction) => {
      const { id, name } = action.payload;

      // You will still need to check if state in `null` and `UNINITIALIZED` before accessing it
      if(state!== null && state !== UNINITIALIZED) {
        state.id = id;
        state.name = name;
      }

      // Or do something like this if you want to disregard previous state
      //state = {id, name};

      return state;
    },
  },
});

export const { setCurrentCustomer } = currentCustomerSlice.actions;

export default currentCustomerSlice.reducer;

Upvotes: 4

Related Questions