I've just built my first app with RTK. Everything was working fine so I thought I would challenge myself and convert it to TypeScript. Converting my first component, I've gotten rid of all the errors except one. I guess I'm struggling to work out where to define the type.
In my extraReducers
, I have two rejected cases from two async actions. The error I am getting is pretty simple, Object is of type 'unknown'
on my payload
properties inside the rejected cases. But I can't work out where to define the payload Object. Any help is greatly appreciated. Code below
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { RootState } from 'rootReducer';
import Axios from 'utils/axios';
interface ForgotPasswordState {
status: {
isPending: boolean;
isSuccess: boolean;
isError: boolean;
errorMessage: string;
},
}
export interface ForgotPasswordRequestProps {
email: string;
}
export interface ForgotPasswordSetProps {
query: string | null;
password: string
}
export const forgotPasswordRequest = createAsyncThunk(
'auth/forgotPasswordRequest',
async (
data: ForgotPasswordRequestProps,
{ rejectWithValue }) => {
const { email } = data;
try {
const response = await Axios.anon.post(`/v1/auth/forgot-password`, {
email,
});
if (response.status === 204) {
return null;
}
} catch (error) {
if (error instanceof Error) {
return rejectWithValue(error.message);
}
return console.log('ERROR', error);
}
}
);
export const forgotPasswordSet = createAsyncThunk(
'auth/verifyEmailSet',
async (
data: ForgotPasswordSetProps,
{ rejectWithValue }) => {
const { query, password } = data;
try {
const response = await Axios.anon.post(`/v1/auth/reset-password?token=${query}`, {
password,
});
if (response.status === 204) {
return null;
}
} catch (error) {
if (error instanceof Error) {
return rejectWithValue(error.message)
}
return console.log("ERROR", error)
}
}
);
const initialState: ForgotPasswordState = {
status: {
isPending: false,
isSuccess: false,
isError: false,
errorMessage: '',
},
}
export const forgotPasswordSlice = createSlice({
name: 'forgotPassword',
initialState,
reducers: {
clearState: (state) => {
state.status.isError = false;
state.status.isSuccess = false;
state.status.isPending = false;
state.status.errorMessage = '';
return state;
},
},
extraReducers: builder => {
builder
.addCase(
forgotPasswordRequest.fulfilled, (state) => {
state.status.isPending = false;
state.status.isSuccess = true;
})
.addCase(
forgotPasswordRequest.rejected, (state, action ) => {
const { payload } = action;
state.status.isPending = false;
state.status.isError = true;
// "Object is of type 'unknown'" on payload below
state.status.errorMessage = payload.message;
})
.addCase(
forgotPasswordRequest.pending, (state) => {
state.status.isPending = true;
})
.addCase(
forgotPasswordSet.fulfilled, (state) => {
state.status.isPending = false;
state.status.isSuccess = true;
})
.addCase(
forgotPasswordSet.rejected, (state, action ) => {
const { payload } = action;
state.status.isPending = false;
state.status.isError = true;
// "Object is of type 'unknown'" on payload below
state.status.errorMessage = payload.message;
})
.addCase(
forgotPasswordSet.pending, (state) => {
state.status.isPending = true;
})
}
});
export const { clearState } = forgotPasswordSlice.actions;
export const statusSelector = (state: RootState) => state.auth.forgotPassword.status;
What I've tried
As suggested, I've tried setting my case to
(state, action: { payload: ErrorPayload }) => {...}
And creating (I think) an appropriate type
export interface ErrorPayload {
message: string
}
but this just results in
No overload matches this call.
Overload 1 of 2, '(actionCreator: AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>, reducer: CaseReducer<ForgotPasswordState, PayloadAction<...>>): ActionReducerMapBuilder<...>', gave the following error.
Argument of type '(state: WritableDraft<ForgotPasswordState>, action: { payload: ErrorPayload; }) => void' is not assignable to parameter of type 'CaseReducer<ForgotPasswordState, PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } & ({ ...; } | ({ ...; } & {})), SerializedError>>'.
Types of parameters 'action' and 'action' are incompatible.
Type 'PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } & ({ rejectedWithValue: true; } | ({ ...; } & {})), SerializedError>' is not assignable to type '{ payload: ErrorPayload; }'.
Types of property 'payload' are incompatible.
Type 'unknown' is not assignable to type 'ErrorPayload'.
Overload 2 of 2, '(type: string, reducer: CaseReducer<ForgotPasswordState, Action<string>>): ActionReducerMapBuilder<ForgotPasswordState>', gave the following error.
Argument of type 'AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>' is not assignable to parameter of type 'string'.
Also as suggested, I've tried setting my case to
(state, action: PayloadAction<string>) => {...}
and importing PayloadAction
from '@reduxjs/toolkit'
. This is also didn't work and resulted in the following errors
No overload matches this call.
Overload 1 of 2, '(actionCreator: AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>, reducer: CaseReducer<ForgotPasswordState, PayloadAction<...>>): ActionReducerMapBuilder<...>', gave the following error.
Argument of type '(state: WritableDraft<ForgotPasswordState>, action: { payload: string; type: string; }) => void' is not assignable to parameter of type 'CaseReducer<ForgotPasswordState, PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } & ({ ...; } | ({ ...; } & {})), SerializedError>>'.
Types of parameters 'action' and 'action' are incompatible.
Type 'PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } & ({ rejectedWithValue: true; } | ({ ...; } & {})), SerializedError>' is not assignable to type '{ payload: string; type: string; }'.
Types of property 'payload' are incompatible.
Type 'unknown' is not assignable to type 'string'.
Overload 2 of 2, '(type: string, reducer: CaseReducer<ForgotPasswordState, { payload: string; type: string; }>): ActionReducerMapBuilder<ForgotPasswordState>', gave the following error.
Argument of type 'AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>' is not assignable to parameter of type 'string'.
CodePudding user response:
In your action, change it to
// Assuming that your expected action is only a message string
// If it's more than that, then change the PayloadAction type param to your expected interface
// You'll need to import PayloadAction from '@reduxjs/toolkit'
.addCasePasswordRequest.rejected, (state, action: PayloadAction<string>) => {
state.status.isPending = false
state.status.isError = true
state.status.errorMessage = action.payload
Also, in order to reduce the amount of data, you may want to create an enum
enum RequestStatus {
Pending = 'PENDING',
Error = 'ERROR',
Success = 'SUCCESS',
}
interface ForgotPasswordState {
status: RequestStatus
errorMessage: string
}
Then in your various reducers, instead of setting isPending
, isError
, and isSuccess
and calling those individually, you can just set it as
state.status = RequestStatus.Error
state.errorMessage = action.payload
Your selector doesn't need to change but anything subscribed to the slice would need to change logic from
if (forgotPasswordStatus.isError) {...}
// Change to
if (forgotPasswordStatus.status === RequestStatus.Error) {...}
CodePudding user response:
Going by the Usage with TypeScript docs for createAsyncThunk
const forgotPasswordRequest = createAsyncThunk<
// Return type of the payload creator
unknown,
// First argument to the payload creator
ForgotPasswordSetProps,
{
// Optional fields for defining thunkApi field types
rejectValue: { message: string }
}
>('auth/verifyEmailSet',
async (
data,
{ rejectWithValue }) => {
const { query, password } = data;
try {
const response = await Axios.anon.post(`/v1/auth/reset-password?token=${query}`, {
password,
});
if (response.status === 204) {
return null;
}
} catch (error) {
if (error instanceof Error) {
return rejectWithValue(error.message)
}
return console.log("ERROR", error)
}
}
);
Specifying that generic there will take care of it everywhere else.