Reputation:
I cannot set the return type of getState()
to RootState
. I'm using typescript and VSCode. I have to set the type to any
, which stops IntelliSense on that object. Below is the code that has the problem:
export const unsubscribeMeta = createAsyncThunk(
'meta/unsubscribe',
async (_, { getState }) => {
const { meta } = getState() as any;
const res = await client.post<apiUnsubscribeResponse>(
`/meta/unsubscribe/${meta.subscriptionId}`
);
return res.data.data;
}
);
If I try to use RootState
instead of any
, many errors are flagged in the module by VSCode. I believe it is due to a circular dependency with the store and this slice. I am using RootState
in many places further down in the module for selectors, with no problem. Is there a way around this?
Upvotes: 32
Views: 25357
Reputation: 2594
The best way is to create a pretyped async thunk somewhere in a utility file as documentation suggests:
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState
dispatch: AppDispatch
rejectValue: string
// extra: { s: string; n: number } // This is extra data prop, can leave it out if you are not passing extra data
}>()
After doing this you will have all of your types automatically in all thunks that you create using createAppAsyncThunk
So in OP's case it would look like this:
export const unsubscribeMeta = createAppAsyncThunk (
'meta/unsubscribe',
async (_, { getState }) => {
const { meta } = getState() // This is typed automatically 🎉
const res = await client.post<apiUnsubscribeResponse>(
`/meta/unsubscribe/${meta.subscriptionId}`
);
return res.data.data;
}
);
Upvotes: 8
Reputation: 301
I will continue on NearHuscarl answer since I can't suggest an edit to it.
NearHuscarl answer is great but the problem with it that he set the options
type to any
, so it solves a problem it raises another since now if you use options
in createAsyncThunk
you have to set all of its types manually or typescript
will raise Binding element implicitly has an 'any' type.
error.
So simply setting options
type like below would solve that problem.
declare module "@reduxjs/toolkit" {
type AsyncThunkConfig = {
state?: unknown;
dispatch?: Dispatch;
extra?: unknown;
rejectValue?: unknown;
serializedErrorType?: unknown;
};
function createAsyncThunk<
Returned,
ThunkArg = void,
ThunkApiConfig extends AsyncThunkConfig = { state: RootState } // here is the magic line
>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<
Returned,
ThunkArg,
ThunkApiConfig
>,
options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>,
): AsyncThunk<Returned, ThunkArg, ThunkApiConfig>;
}
Upvotes: 5
Reputation: 81520
You can use Typescript's module augmentation feature to assign the default state to AsyncThunkConfig.state
which will be the returned type of getState()
when we call it later on.
declare module "@reduxjs/toolkit" {
type AsyncThunkConfig = {
state?: unknown;
dispatch?: Dispatch;
extra?: unknown;
rejectValue?: unknown;
serializedErrorType?: unknown;
};
function createAsyncThunk<
Returned,
ThunkArg = void,
ThunkApiConfig extends AsyncThunkConfig = {
state: YourRootState; // this line makes a difference
}
>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<
Returned,
ThunkArg,
ThunkApiConfig
>,
options?: any
): AsyncThunk<Returned, ThunkArg, ThunkApiConfig>;
}
Where YourRootState
is the type of your store state.
type YourRootState = {
myNumber: number;
myString: string;
};
Now you can use createAsyncThunk
as usual and getState()
returns the correct type.
const doSomethingAsync = createAsyncThunk(
"mySlice/action",
async (_, { getState, dispatch }) => {
const rootState = getState(); // has type YourRootState
console.log(rootState.myNumber);
console.log(rootState.myString);
}
);
function Child() {
const dispatch = useDispatch();
return <button onClick={() => dispatch(doSomethingAsync())}>Click</button>;
}
Upvotes: 17
Reputation: 715
The createAsyncThunk
can have the types defined on the generics:
export const unsubscribeMeta = createAsyncThunk<apiUnsubscribeResponse, void, {state: RootState }>(
'meta/unsubscribe',
async (_, { getState }) => {
const { meta } = getState();
const res = await client.post<apiUnsubscribeResponse>(
`/meta/unsubscribe/${meta.subscriptionId}`
);
return res.data.data;
}
);
Defining the state
will automatically make the getState be aware of the application state.
Upvotes: 39
Reputation: 22670
Simply omit state: RootState
from your ThunkApiConfig
type, then you can use const state = getState() as RootState;
in your payloadCreator without circular dependency.
Upvotes: 7
Reputation: 42228
You don't really need to know about the shape of the entire state. You just need to know about the presence of the values which you are trying to access.
If you can access the whole state.meta
type:
const { meta } = getState() as { meta: MetaState };
If not:
const { meta } = getState() as { meta: { subscriptionId: string } };
I recommend this sort of approach for avoiding the circular dependency because the root state will always depend on the slices, so the slices should not depend on the root.
Upvotes: 28