Home > Net >  Typescript | missing property in generic indexable type
Typescript | missing property in generic indexable type

Time:04-20

I was was curious how Redux Toolkit works under the hood and tried to replicate a basic implementation. But I ran into an issue regarding the indexable type AnyAction and the type PayloadAction that has a defined property called payload.

The builder.addCase function expects a reducer function as the second parameter. This reducer in turn has 2 parameters state & action. The action parameter defaults to AnyAction. The moment I manualy set the type of action to PayloadAction<P> an error pops up regarding the missing property payload in type Action<string>.

I cannot seem to get my head around it and was hoping someone could help me out and explain to me why this does not work.

link to TS playground

export interface Action<T extends any = string> {
    type: T
}

export interface AnyAction extends Action {
    [extraProps: string]: any
}

export interface PayloadAction<P = any> extends Action {
    payload: P
}

const builder = {
    addCase(
        typeOrActionCreator: string, reducer: <A extends Action = AnyAction>(state: any, action: A) => void
    ) {

        return builder
    }
}

builder.addCase('test', (state, action: PayloadAction<number>) => {
    action.payload
}).addCase('tester', (state, action) => {
    action
})

CodePudding user response:

The way you are using this, addCase() should be generic and the reducer argument is not generic, it just uses the type parameter from addCase().

Try this:

const builder = {
    addCase<A extends Action = AnyAction>(
        typeOrActionCreator: string,
        reducer: (state: any, action: A) => void
    ) {
        return builder
    }
}

You should put the generic where you want to the type to be inferred. In your case when you call addCase() you infer the payload type, and from then on the reducer function will always use that payload type. It's locked in and you cannot pass a different type as the payload. That's why reducer is not a generic function.

Playground

CodePudding user response:

You can use createAction and passing the type for payload like this:

const incrementByAmount = createAction<number, 'increment'>('increment')

Now if you hover over the action, you will see the type will be increment & payload will be number.

In redux toolkit, they already have a example how you can set the type of payload. https://redux-toolkit.js.org/api/createreducer

Complete Example From Redux Toolkit Documentation.

import { createAction, createReducer } from '@reduxjs/toolkit'

interface CounterState {
  value: number
}

const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')
const incrementByAmount = createAction<number>('counter/incrementByAmount')

const initialState = { value: 0 } as CounterState

const counterReducer = createReducer(initialState, (builder) => {
  builder
    .addCase(increment, (state, action) => {
      state.value  
    })
    .addCase(decrement, (state, action) => {
      state.value--
    })
    .addCase(incrementByAmount, (state, action) => {
      state.value  = action.payload
    })
})
  • Related