The thunkAPI.getState method does not correctly infer the state type

Viewed 4746

Code here:

users.slice.ts:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

const userAPI = {
  async fetchById(userId: string) {
    return { data: { id: userId, name: 'teresa teng' } };
  },
};

export const fetchUserById = createAsyncThunk<User, string>(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId);
    return response.data;
  },
  {
    condition: (userId, { getState }) => {
      const { users } = getState();
      const { fetchStatus } = users[userId];
      if (fetchStatus === 'loading') {
        return false;
      }
    },
  }
);

interface User {
  name: string;
  id: string;
}

interface UsersSliceState {
  [id: string]: { fetchStatus: 'idle' | 'loading'; data: User };
}

const usersSliceState: UsersSliceState = {
  1: {
    fetchStatus: 'idle',
    data: { name: '', id: '' },
  },
};

const usersSlice = createSlice({
  name: 'users',
  initialState: usersSliceState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUserById.fulfilled, (state, action) => {
      console.log(action);
    });
  },
});

export default usersSlice.reducer;

store.ts:

import { configureStore } from '@reduxjs/toolkit';
import usersSliceReducer from './users.slice';

export const store = configureStore({
  reducer: {
    users: usersSliceReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;

main.ts:

import { store } from './store';
import { fetchUserById } from './users.slice';

store.dispatch(fetchUserById('1'));

TSC throws an error when I call getState() method and deconstruct the users state slice inside condition function

Property 'users' does not exist on type '{}'.ts(2339)

The RootState type inferred is:

type RootState = {
    users: UsersSliceState;
}

How should I make this type correctly? If I import the RootState type and type cast the return value of getState(), will it cause circular references?

package versions:

"@reduxjs/toolkit": "^1.5.0",
"typescript": "^4.1.2"
3 Answers

You need to specify the state in createAsyncThunk to be RootState, where state is part of the optional fields for defining thunkApi field types.

export const fetchUserById = createAsyncThunk<
 User,
 string, 
 { state: RootState}
>(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId);
    return response.data;
  },
  {
    condition: (userId, { getState }) => {
      const { users } = getState();
      const { fetchStatus } = users[userId];
      if (fetchStatus === 'loading') {
        return false;
      }
    },
  }
);

Seems like the approach used in @types/react-redux for defining a typed alias of the useSelector hook (TypedUseSelectorHook) can be used in this case too:

type TypedCreateAsyncThunk<ThunkApiConfig> = <Returned, ThunkArg = void>(
  typePrefix: string,
  payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>,
  options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>
) => AsyncThunk<Returned, ThunkArg, ThunkApiConfig>;

export const createAppAsyncThunk: TypedCreateAsyncThunk<{
  state: RootState;
}> = createAsyncThunk;

To avoid circular dependencies, how about something like this?

Instead of

createAsyncThunk<User, string>

Use

createAsyncThunk<User, string, {state: {users: UserSliceState}}>
Related