user10832440
user10832440

Reputation:

Cannot set getState type to RootState in createAsyncThunk

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

Answers (6)

Evaldas B
Evaldas B

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

Ahmed Hamed
Ahmed Hamed

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

NearHuscarl
NearHuscarl

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>;
}

Live Demo

Edit 64793504/cannot-set-getstate-type-to-rootstate-in-createasyncthunk

Upvotes: 17

Jo&#227;o Baraky
Jo&#227;o Baraky

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

thSoft
thSoft

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

Linda Paiste
Linda Paiste

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

Related Questions