Skip to content

Type 'CaseReducerWithPrepare' constrains its generic #688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
8times12 opened this issue Aug 11, 2020 · 2 comments · May be fixed by Bhanditz/redux-starter-kit#1
Closed

Type 'CaseReducerWithPrepare' constrains its generic #688

8times12 opened this issue Aug 11, 2020 · 2 comments · May be fixed by Bhanditz/redux-starter-kit#1

Comments

@8times12
Copy link

On typings.d.ts file of @redux/toolkit, a generic Action of type CaseReducerWithPrepare extends type PayloadAction with default generics and constrains its method prepare to return void. However, a method prepare of a payload action seems able to return any. Does this type declaration fit the entity of the API?

PayloadAction:

export declare type PayloadAction<P = void, T extends string = string, M = never, E = never> = {
    payload: P;
    type: T;
} & ([M] extends [never] ? {} : {
    meta: M;
}) & ([E] extends [never] ? {} : {
    error: E;
});

CaseReducerWithPrepare:

export declare type CaseReducerWithPrepare<State, Action extends PayloadAction> = {
    reducer: CaseReducer<State, Action>;
    prepare: PrepareAction<Action['payload']>;
};

It looks like <any> is required after Action extends PayloadAction.

@8times12 8times12 changed the title Type 'CaseReducerWithPrepare' unreasonably constrains its generic Type 'CaseReducerWithPrepare' constrains its generic Aug 11, 2020
@phryneas
Copy link
Member

phryneas commented Aug 11, 2020

With all the possible ways to type that there, there's unfortunately no possible way to infer that correctly.

Instead, we are first allowing some pretty permissive types to enter almost everything and then use

export type ValidateSliceCaseReducers<
  S,
  ACR extends SliceCaseReducers<S>
> = ACR &
  {
    [T in keyof ACR]: ACR[T] extends {
      reducer(s: S, action?: infer A): any
    }
      ? {
          prepare(...a: never[]): Omit<A, 'type'>
        }
      : {}
  }

to make sure that the returned partial action of prepare and the action of the reducer are actually compatible.

This requires for the action parameter in reducer to be typed while it allows the return value of prepare to be either typed implicitly or explicitly - as long as the types are compatible.

For a usage example, see https://redux-toolkit.js.org/usage/usage-with-typescript#defining-action-contents-with-prepare-callbacks

If you have any specific situations where that doesn't work, let us know.

Also, all that is probably getting a lot easier once we get #637 on the road, as then you'll have an alternative notation like

createSlice({
  name,
  initialState,
  reducers: create => ({ // callback form
    fetchTodos: create.asyncThunk(
      todosAPI.fetchTodos,
      {pending, fulfilled, rejected} // thunk reducers here
    ),
    toggleTodo(state, action: PayloadAction<string>) { // "normal" reducer in this object
      state[index].completed = !state[index].completed;
    },
    addTodo: create.preparedReducer( // this will be much better typed
      (text) => ({payload: {text, id: nanoid()}}),
      (state, action) => void state.push(action.payload);
    )
  })

which infers a lot more.

@8times12
Copy link
Author

Thank you for your kind explanation. Thank you for your kind explanation. It has been resolved.
I am looking forward to the completion of #637.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants