Struggling to understand how to use a discriminated union type with a reducer. Here is how my reducer is setup below. I want to be able to pass this into a provider component, but keep hitting type errors.
export const enum LoadingState {
idle = "idle",
loading = "loading",
loaded = "loaded",
error = "error",
}
export type SampleState =
| {
status: LoadingState.idle;
}
| {
status: LoadingState.loading;
}
| {
status: LoadingState.loaded;
sampleDetails: SampleDetails;
}
| {
status: LoadingState.error;
error: any;
};
export const INITIAL_STATE: SampleState = {
status: LoadingState.idle,
};
export type Action =
| {
type: "SET_SAMPLE_LOADING_STATE";
payload: { status: LoadingState };
}
| {
type: "SET_SAMPLE";
payload: { sampleDetails: SampleDetails };
};
export const reducer = (state: SampleState, action: Action) => {
switch (action.type) {
case "SET_SAMPLE_LOADING_STATE":
return {
...state,
status: action.payload.status,
};
case "SET_SAMPLE":
return {
...state,
status: LoadingState.loaded,
sampleDetails: action.payload.sampleDetails,
};
default:
return state;
}
};
// Trying to use it here in a provider
const [state, dispatch] = useReducer(
reducer,
INITIAL_STATE
);
But I keep getting:
Argument of type 'SampleState' is not assignable to parameter of type 'never'.
Type '{ status: LoadingState.idle; }' is not assignable to type 'never
Is there something wrong with my setup here? If I remove the other types from the union, it works as expected.
Since you're merging the previous state with the next one, if the previous state was "error", and you use the
"SET_SAMPLE_LOADING_STATE"action to set it to "idle", you could end up with an invalid state like:I think you can fix this by explicitly making
reducerreturnSampleState, and having it make more fine-grained checks for which state you're setting, e.g.: