reduxjs/redux-toolkit

Type incompatibility for preloadedState in configureStore

itwaze opened this issue · 2 comments

I have a big project with a lot of "old way implementation" for reducers (switch case) and inferred actions to have payload tips in my reducers.

Currently I would like to migrate partially to RTK using only configureStore instead of createStore, I want to stay with my current reducers, actions etc, but I noticed strange type incompatibility for preloadedState.

Definitions:

// action types
export const SET_PROIPERTY_X = "SET_PROIPERTY_X" as const;
export const RESET_STATE = "RESET_STATE" as const;
// action creators
import { RESET_STATE, SET_PROIPERTY_X } from "./actionTypes";

export const setPropertyX = (value: string) => {
  return {
    type: SET_PROIPERTY_X,
    value,
  };
};

export const resetState = () => {
  return {
    type: RESET_STATE,
  };
};
// reducer
import { RESET_STATE, SET_PROIPERTY_X } from "./actionTypes";
import { AwesomeAction, AwesomeState } from "./types";

export const initialState: AwesomeState = {
  propertyX: "X",
};

export const awesomeSlice = (
  state: AwesomeState = initialState,
  action: AwesomeAction
) => {
  switch (action.type) {
    case SET_PROIPERTY_X: {
      return {
        ...state,
        propertyX: action.value,
      };
    }
    case RESET_STATE: {
      return initialState;
    }
    default: {
      return state;
    }
  }
};
// types
import * as actions from "./actionCreators";

export interface AwesomeState {
  propertyX: string;
  propertyY?: string;
  propertyZ?: string;
}
export type AwesomeAction = ReturnType<(typeof actions)[keyof typeof actions]>;

Expected behavior:
Simply add preloaded state to one of my slices (how it was before with createStore)

const rootReducer = combineReducers({
  awesomeSlice,
});

const store = configureStore({
  reducer: rootReducer,
  preloadedState: {
    awesomeSlice: { proprertyX: "XXX" },
  },
});

Current behavior
Getting warnings because of return type of rootReducer
image

image

As a workaround I can do like that:
image

but I would like to follow configureStore API and simply provide preloadedState.

Codesandbox

Your reducer is a lie to TypeScript.

It will in runtime definitely be called with every action that is ever dispatched in your application, so defining it as

export const awesomeSlice = (
  state: AwesomeState = initialState,
  action: AwesomeAction
) => {
  // ...
};

is just incorrect.

Instead, you have to also allow UnknownAction as action argument.

The downside of that is that - also technically correct, your payload within the reducer will always be unknown now.

One way of doing that would be something like

// @ts-expect-error inner signature is still lying to the compiler, but at least we're correct to the outside now
export function awesomeSlice(
  state: AwesomeState | undefined,
  action: UnknownAction
): AwesomeState;
export function awesomeSlice(
  state: AwesomeState = initialState,
  action: AwesomeAction
) {
  // ...
}

but really, I'd recommend to switch over to type guard functions instead of switch..case, or doing a deliberate as cast of your action type inside of your reducer - all that assuming you can't switch over to createSlice of course.

still tricky but yeah, I got it

thanks