Search code examples
reduxreact-reduxredux-thunkredux-toolkitrtk-query

changing state with RTK Query


I'm learning about RTK Query and really confused. I'd be happy if someone could point me towards the right direction. My question is how one can manipulate the state of the application store the same way as it is done when using createAsyncThunk and setting up extraReducers.

export const asyncApiCall = createAsyncThunk("api/get_data", async (data) => {
    
    const config = {
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
        }
   
    };
    
    const res = await axios.get( "http://apiserver/path_to_api",data,config );
    return res['data']
} )

export const mySlice  = createSlice({
    name:"mySliceName",
    initialState:{
        data: [],
        loadInProgress: false,
        loadError: null,
        extraData: {
           // something derived based on data received from the api 
        }

    },

    extraReducers: {
        [asyncApiCall .pending]: (state) => {
            state.loadInProgress = true;
    
        },
        [asyncApiCall .fulfilled]: (state,action) => {
            
            state.loadInProgress = false;
            state.data = action.payload;

            state.extraData = someUtilFunc(state.data)
           
        },
        [asyncApiCall.rejected]: (state) => {
            state.loadInProgress = false;
            state.loadError= true;
        },
       
    }
 })

Now I'm replacing it with RTK Query. My current understanding is that RTK Query automatically generates hooks for exposing data received from the api and all the query-related info like if it's pending, if an error occurred etc.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'


export const apiSlice = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: builder => ({
    getData: builder.query({
      query: () => '/get_data'
    }),

    setData: builder.mutation({
        query: info => ({
          url: '/set_data',
          method: 'POST',
          body: info 
        })
      })

  })
})

export const { useSendDataMutation, useGetDataQuery } = apiSlice 

If I want to store some additional data that may be affected by the api calls should I create another slice that will somehow interact with the apiSlice, or is it possible to incorporate everything in this existing code? I'm sorry for possible naivety of this question.


Solution

  • The short answer is that RTK Query is focused on purely caching data fetched from the server. So, by default, it stores exactly what came back in an API call response, and that's it.

    There are caveats to this: you can use transformResponse to modify the data that came back and rearrange it before the data gets stored in the cache slice, and you can use updateQueryData to manually modify the cached data from other parts of the app.

    The other thing to note is that RTK Query is built on top of standard Redux patterns: thunks and dispatched actions. Every time an API call returns, a fulfilled action gets dispatched containing the data. That means you can also apply another suggested Redux pattern: listening for that action in other reducers and updating more than one slice of state in response to the same action.

    So, you've got three main options here:

    • If the "extra data" is derived solely from the server response values, you could use transformResponse and return something like {originalData, derivedData}
    • You could just keep the original data in the cache as usual, but use memoized selector functions to derive the extra values as needed
    • If you might need to update the extra values, then it's probably worth looking at listening to a query fulfilled action in another slice and doing something with it, like this silly example:
    import { api } from "./api";
    
    const someExtraDataSlice = createSlice({
      name: "extraData",
      initialState,
      reducers: {/* some reducers here maybe? */},
      extraReducers: (builder) => {
        builder.addMatcher(api.endpoints.getPokemon.matchFulfilled, (state, action) => {
          // pretend this field and this payload data exist for sake of example
          state.lastPokemonReceived = action.payload.name;
        }
      }
    })