For following code (see Playground Link) of handler functions in an object I use the Record type:
interface User {
id: string;
avatar: string;
email: string;
name: string;
role?: string;
[key: string]: any;
}
interface State {
isInitialized: boolean;
isAuthenticated: boolean;
user: User | null;
}
type InitializeAction = {
type: 'INITIALIZE';
payload: {
isAuthenticated: boolean;
user: User | null;
};
};
type LoginAction = {
type: 'LOGIN';
payload: {
user: User;
isAuthenticated: boolean;
};
};
type LogoutAction = {
type: 'LOGOUT';
};
type RegisterAction = {
type: 'REGISTER';
payload: {
user: User;
};
};
type Action =
| InitializeAction
| LoginAction
| LogoutAction
| RegisterAction;
const handlers: Record<string, (state: State, action: Action) => State> = {
INITIALIZE: (state: State, action: InitializeAction): State => {
const {
isAuthenticated,
user
} = action.payload;
return {
...state,
isAuthenticated,
isInitialized: true,
user
};
},
LOGIN: (state: State, action: LoginAction): State => {
const { user } = action.payload;
return {
...state,
isAuthenticated: true,
user
};
},
LOGOUT: (state: State): State => ({
...state,
isAuthenticated: false,
user: null
}),
REGISTER: (state: State, action: RegisterAction): State => {
const { user } = action.payload;
return {
...state,
isAuthenticated: true,
user
};
}
};
I do get following error for the handler functions though:
TS2322: Type '(state: State, action: InitializeAction) => State' is not assignable to type '(state: State, action: Action) => State'.
Types of parameters 'action' and 'action' are incompatible.
Type 'Action' is not assignable to type 'InitializeAction'.
Type 'LoginAction' is not assignable to type 'InitializeAction'.
Types of property 'type' are incompatible.
Type '"LOGIN"' is not assignable to type '"INITIALIZE"'.
I believe this is because of contravariance. Here you can find more information about this topic and how it works in typescript.
In order to type your handler
object, you should use mapped types:
interface User {
id: string;
avatar: string;
email: string;
name: string;
role?: string;
[key: string]: any;
}
interface State {
isInitialized: boolean;
isAuthenticated: boolean;
user: User | null;
}
type InitializeAction = {
type: 'INITIALIZE';
payload: {
isAuthenticated: boolean;
user: User | null;
};
};
type LoginAction = {
type: 'LOGIN';
payload: {
user: User;
isAuthenticated: boolean;
};
};
type LogoutAction = {
type: 'LOGOUT';
};
type RegisterAction = {
type: 'REGISTER';
payload: {
user: User;
};
};
type Action =
| InitializeAction
| LoginAction
| LogoutAction
| RegisterAction;
// type Handlers = {
// INITIALIZE: (state: State, action: InitializeAction) => State;
// LOGIN: (state: State, action: LoginAction) => State;
// LOGOUT: (state: State, action: LogoutAction) => State;
// REGISTER: (state: State, action: RegisterAction) => State;
// }
type Handlers = {
[Type in Action['type']]: (state: State, action: Extract<Action, { type: Type }>) => State
}
const handlers: Handlers = {
INITIALIZE: (state, action) => {
const {
isAuthenticated,
user
} = action.payload;
return {
...state,
isAuthenticated,
isInitialized: true,
user
};
},
LOGIN: (state, action) => {
const { user } = action.payload;
return {
...state,
isAuthenticated: true,
user
};
},
LOGOUT: (state) => ({
...state,
isAuthenticated: false,
user: null
}),
REGISTER: (state, action) => {
const { user } = action.payload;
return {
...state,
isAuthenticated: true,
user
};
}
};
Here you can find related answer.
See this example:
type Reducer = (state: State, action: Action) => State;
const reducer: Reducer = (state, action) => state
type ContravariantReducer = Record<string, Reducer>
// Arrow of inheritance has changed in an opposite way
const contravariant: ContravariantReducer = {
initialize: (state: State, action: InitializeAction) => state
}
// Arrow of inheritance has changed in an opposite way
const contravariant2: Record<string, (state: State, action: InitializeAction) => State> = {
initialize: (state: State, action: Action) => state
}
P.S. Apart from typescript type issues, I strongly believe that you should type your reducer according to redux docs provided by @Dima Parzhitsky