redux-toolkit: createReducer does not infer action types

When I create a reducer, the action type does not get inferred from the action. Example:

const myAction = createAction<boolean>('my/action')
const reducer = createReducer(false, {
   // action is of type "any" (and then so is the payload)
  [myAction.type](state, action) {
    return action.payload
  },
})

I am currently rolling my own setup, where I’ve got this type of inference working. Though I’m using the payload rather than the whole action object as a parameter for my callbacks.

In my case, I have to type the action like this through an extra function invocation, though I’m not sure how necessary this is.

const action = createAction('my/action')<boolean>()

I don’t pretend to understand how all of this works, but maybe someone can figure out a way to get the best of both worlds? What follows is pretty much my whole setup for this.

import { produce, Draft } from 'immer'
import { EmptyAction, PayloadAction,  createAction as createTypesafeAction } } from 'typesafe-actions'

export default function createAction<T extends string>(type: T) {
  return function<P extends any = undefined>() {
    return Object.assign(createTypesafeAction(type)<P>(), { type })
  }
}

export type Action<T extends string = string, P = never> = EmptyAction<T> | PayloadAction<T, P>

export type Producer<S, A extends Action> = A extends PayloadAction<any, infer P>
  ? (draft: Draft<S>, payload: P) => void | S
  : (draft: Draft<S>) => void | S

export type Producers<S, A extends Action> = A extends PayloadAction<any, any>
  ? { [T in A['type']]: Producer<S, A> }
  : { [T in A['type']]: Producer<S, A> }

export default function createReducer<S, A extends Action>(defaultState: S, producers: Producers<S, A>) {
  return function reducer(state = defaultState, action: A) {
    return produce(state, draft => {
      if (action.type in producers) {
          return producers[action.type](draft, action.payload)
      }
    })
  }
}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 1
  • Comments: 17 (5 by maintainers)

Most upvoted comments

@Glinkis you could do this:

const increment = createAction('counter/increment', (n: number) => ({ payload: n }))
const decrement = createAction('counter/decrement', (n: number) => ({ payload: { n } }))

const counterReducer = createReducer(0, {
  [increment.type]: (state, action: ReturnType<typeof increment>) => state + action.payload,
  [decrement.type]: (state, action: ReturnType<typeof decrement>) => state - action.payload.n,
})

Given the weirdness of JS’s switch statement, can you actually do that as a switch? Something like this:

You can, but it might be a bit too clever. If/else ishould be more idiomatic in that case.