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.
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.
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
})
})