I'm trying to type an higher order reducer and managed to narrow down the problem, the problem is from state
type in redux
type definition of Reducer
which is S | undefined
, If i delete undefined
and just type state
as S
everything works as expected
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
The problem is that the higher order reducer doesn't initialize the state with a default value which is required by redux
as it dispatches an action that doesn't match in any reducer to populate the store with an initial value but i guess typescript cant tell that the inner reducer will handle that.
What are my options here besides removing undefined from the type definition?
The type of movie in src > reducers.ts > RootState
at the bottom is never
Higher order reducer
import { Action } from "redux";
const startReducer = <S>(state: S): S => ({
...state,
loading: true,
error: false
});
const successReducer = <S>(state: S): S => ({
...state,
loading: false
});
const errorReducer = <S>(state: S): S => ({
...state,
error: true,
loading: false
});
type LoadingActionTypes = Record<"start" | "success" | "error", string>;
const withLoadingStates = ({ start, success, error }: LoadingActionTypes) => {
const actionReducerMapper = {
[start]: startReducer,
[success]: successReducer,
[error]: errorReducer
};
return <S, A extends Action>(baseReducer: (state: S, action: A) => S) => (
state: S,
action: A
): S => {
const nextState = actionReducerMapper[action.type]
? actionReducerMapper[action.type](state)
: state;
return baseReducer(nextState, action);
};
};
export default withLoadingStates;
Inner reducer
import { combineReducers } from "redux";
import withLoadingStates from "./withLoadingStates";
const FETCH_MOVIE_BY_ID_START = "FETCH_MOVIE_BY_ID_START";
const FETCH_MOVIE_BY_ID_SUCCESS = "FETCH_MOVIE_BY_ID_SUCCESS";
const FETCH_MOVIE_BY_ID_ERROR = "FETCH_MOVIE_BY_ID_ERROR";
type FetchMovieByIdStartAction = { type: typeof FETCH_MOVIE_BY_ID_START };
type FetchMovieByIdSuccessAction = { type: typeof FETCH_MOVIE_BY_ID_SUCCESS };
type FetchMovieByIdErrorAction = { type: typeof FETCH_MOVIE_BY_ID_ERROR };
type MovieByIdActionTypes =
| FetchMovieByIdStartAction
| FetchMovieByIdSuccessAction
| FetchMovieByIdErrorAction;
type LoadingStates = {
loading: boolean;
error: boolean;
};
const initialState: LoadingStates = {
loading: false,
error: false
};
const movieReducer = (state = initialState, action: MovieByIdActionTypes) =>
state;
const wrappedReducer = withLoadingStates({
start: FETCH_MOVIE_BY_ID_START,
success: FETCH_MOVIE_BY_ID_SUCCESS,
error: FETCH_MOVIE_BY_ID_ERROR
})(movieReducer);
export const rootReducer = combineReducers({
movie: wrappedReducer
});
// movie type is never
export type RootState = ReturnType<typeof rootReducer>;
The error i am getting
(property) movie: Reducer No overload matches this call. Overload 1 of 2, '(reducers: ReducersMapObject<{ movie: LoadingStates; trending: Record; }, any>): Reducer<...>', gave the following error. Type '(state: LoadingStates, action: MovieByIdActionTypes) => LoadingStates' is not assignable to type 'Reducer'. Types of parameters 'state' and 'state' are incompatible. Type 'LoadingStates | undefined' is not assignable to type 'LoadingStates'. Type 'undefined' is not assignable to type 'LoadingStates'.ts(2769)