RTK Query get state from another slice using getState()
Asked Answered
M

2

21

I just started on redux yesterday and after reading up on the different libraries, I decided to use the slice route from RTK.

For my async, instead of using createAsyncThunk, I decided to use RTK query and I have a question on the right way to access state from another slice.

slice1 contains some user data for example:

export const initialState: IUserState = {
   name: 'example',
   id: null,
};

and in my slice2, I have a function that wants to do something like getSomethingByUserId(id) and my current implementation:

interface IApiResponse {
  success: true;
  result: IGotSomethingData[];
}

const getsomethingSlice: any = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://someapibase',
  }),
  endpoints(builder) {
    return {
      fetchAccountAssetsById: builder.query<IApiResponse, null>({
        query() {
          console.log('store can be called here', store.getState().user.id);
          return `/apipath?id=${store.getState().user.id}`;
        },
      }),
    };
  },
});

export default getsomethingSlice;
export const { useFetchAccountAssetsByIdQuery } = getsomethingSlice;

As I read somewhere that markerikson mentioned it's not good practice to import the store but to use getState in thunk, I took a look around and see in the documentations that there is getState for query which exist in the onStart unlike thunk which you can access it from it's second parameter.

Do anyone have a onStart implementation for this? Or is importing store acceptable for this?

Maryleemarylin answered 6/6, 2021 at 5:13 Comment(0)
F
31

Generally, we want to prevent people from doing that, which is why you don't have getStore available there (you have at many other places).

You see, RTK-query uses the argument you give into the query to determine the cache key. Since you don't pass in an argument, the cache key the result would be stored as fetchAccountAssetsById(undefined).

So, you make a first request, state.user.id is 5 and that request is made.

Now, your state.user.id changes to 6. But your component calls useFetchAccountAssetsByIdQuery() and there is already a cache entry for fetchAccountAssetsById(undefined), so that is still being used - and no request is made.

If your component instead would be calling useFetchAccountAssetsByIdQuery(5) and it changes to useFetchAccountAssetsByIdQuery(6), RTK-query can safely identify that it has a cache entry for fetchAccountAssetsById(5), but not for fetchAccountAssetsById(6) and will make a new request, retrieving up-to-date information.

So, you should select that value in your component using useSelector and pass it into your query hook as an argument, not pull it out of the store in your query function.

Floats answered 6/6, 2021 at 10:56 Comment(9)
I see, what about onStart’s getState?Maryleemarylin
onStart is onQueryStarted now, please take a look at the current RC docs. Generally, there you are doing a side effect that takes place after the decision has been made that that query should be running. So the whole thing doesn't play a role there.Floats
@Floats Does your answer also apply for common path parameters? In our case, most endpoints start with users/${id}.Saundrasaunter
@Saundrasaunter usually I'd recommend to wrap your hooks in custom ones, but you can do something like this in a custom wrapped baseQuery. Just be aware that you essentially disable cache for that.Floats
Well, I see. For now I will stick to the pattern to hand over the parameter from the caller to the query like useRecords({ userId: user.id, date: toDateString(selectedDate) }) where as the user.id comes from some state.Saundrasaunter
@Floats is it ok to access state via queryFn api to transform a response?Eneidaenema
@Eneidaenema you have to be aware that if the state changes later, your transformed response will not magically change accordingly. Usually, it would be better to do something like that outside of RTK Query.Floats
@Floats the state I'm accessing is immediately triggering (when changed) the query that I'm transforming, so the query is always up to date. I'm passing a sub-set (array of ids) of that state to the query but perhaps I should just pass the entire state and then I would have access to it via the query arg. The thought was to minimize the amount of data I'm passing.Eneidaenema
@Eneidaenema If your query depends on it, passing it in as an arg would be best.Floats
B
0

You can take some idea from this. it's for changing basequery's headers, according to states.

const baseQuery = fetchBaseQuery({
  baseUrl: 'https://api.your-really-great-app.com/v1/',
  prepareHeaders: (headers, { getState }) => {
    const token = (getState() as RootState).auth.token;
    // If we have a token set in state, let's assume that we should be passing it.
    if (token) {
      headers.set('authorization', `Bearer ${token}`);
    }
    return headers;
  },
})
Broomfield answered 2/1 at 7:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.