adjoe Engineers’ Blog
 /  Frontend  /  RTK Query & Redux: Manage API Data in React
RTK Query & Redux: Best Way to Manage API Data in React
Frontend

RTK Query & Redux: Best Way to Manage API Data in React

When building React applications that fetch data from a server and need a global state, there are different options for request and state management libraries to fetch and save this data. 

Redux and RTK Query have the unique advantage of working together. 

Our team at adjoe recently revamped parts of our Playtime offerwall to use this advantage. 

In the case that you are also using both packages in your application and you have also missed the opportunity to simplify accessing your fetched data throughout your application, you have come to the right place. 

In this post, I introduce you to simple and memoized selectors in Redux, selecting data from RTK Queries, and how to use redux selectors to form unique versions of your global store state that you need in specific pages. 

Select Responses from RTK Queries

Did you know that every response from Redux Toolkit Query is saved in Redux? There is no need to create slices for data that you are fetching from the backend and using dispatch actions to save them.

We can access them in selectors with the same parameters we used to fetch the data, and share them across our whole application.

Let’s say you are working on a website that lets you create a list of your shown movies, and show the favorites of others. On the landing page, we are fetching the favorite movies of the logged-in user.

We created our baseApi “movieApi”, and for cleaner code, we are injecting our api endpoints into it. 

Codeblock 1: GetUserMovies Query with RTK Query

import { movieApi } from './movieApi;

export const userMoviesApi = movieApi.injectEndpoints({

  endpoints: (build) => ({

    getUserMovies: build.query<

      UserMovie[],

      {

        userId: number;

      }

    >({

      query: ({ userId }) => ({

        url: `/v1/movies/user/${userId}`,

      }),

    }),

  }),

});

export const { useGetUserMoviesQuery } = userMoviesApi;

Now, in our landing page, we are executing the query to fetch the saved movies of our user, using the userId we got from the sign-in of the user.

Codeblock 2: Use QueryHook from Created api

const { data: userMovies } = useGetClickedCampaignsQuery({

  userId,

});

Now we have access to the fetched data with the data object.
In this case, it contains the array of userMovies returned from the backend. We can use this across the react components to show the movies fetched. 


But what if we want to show this data in a subcomponent and avoid passing on the movies, especially if we have a larger component tree? Or what if we want to show the same data on another page, but want to avoid refetching the data?

We can either use the same GetQuery hook and set caching on the RTK Query api, or we can get the data from the global storage package we are using, Redux. 

For this, we can write a selector. 

Selectors in Redux are functions that can be passed to the useSelector hook imported from Redux. To get a typed version of it, we can export it like this, the TRootState being exported from our store, and use it throughout our application:

Codeblock 3: Export Typed Selector Hook with Store

export const store = configureStore({

  reducer: {

    [movieApi.reducerPath]: movieApi.reducer,

  }

});

setupListeners(store.dispatch);

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

export const useAppSelector: TypedUseSelectorHook<TRootState> = useSelector;

The selector hook subscribes to changes in the store and updates whenever the selected value changes. We can either use it with an inline function or extract the selector function and use the imported version.

Codeblock 4: Redux Selector Basics

// in the case your store looks like this

export const store = configureStore({

  reducer: {

    exampleValue: exampleValueSlice.reducer,

  }

});

// inline function selector

const exampleValue = useAppSelector(state => state.exampleValue);

const selectExampleValue = (state: TRootState) => state.exampleValue

// extracted function selector

const exampleValue = useAppSelector(selectExampleValue)

For naming conventions, when using extracted functions, these selector functions are commonly prefixed with “select” so it is clear that these are selecting data from the store.

Coming back to our movie example, we now want to select the response from our userMovies Query stored in the Redux store. For this, the data is stored by parameter keys used to fetch this data. 

In our example, if we were to fetch the movies for two different user IDs, there would be two different responses in the store with these different userIds as the key to select them.

Let’s write a simple selector to get the data:

CodeBlock 5: Select an RTK Query Response with createSelector

export const selectUserMoviesResult = (userId: number) => userMoviesApi.endpoints.getUserMovies.select(userId)

export const selectUserMovies = createSelector(

  selectUserMoviesResult,

  (movies) => movies?.data ?? emptyMovies

)

const selectedUserMovies = useAppSelector(selectUserMovies(userId))

You might have noticed the createSelector method in the above example. This is a method included in the Redux Toolkit package that lets us create memoized selectors. 

Normally, a selector triggers a rerender every time something in the store changes. In some cases, we want to avoid unnecessary recalculations and rerenders to keep improving the performance of the application, especially if you have a lot of data in the store that is rapidly changing. 

createSelector takes other selectors as an input, and only rerenders when the response of these inputs changes. That means we can specifically target data in the store and memoize around it. This is especially useful when the selector executes expensive logic to transform the data coming from the store.

Transform Data from the Store in Selectors

The Redux Store should only contain the data we need. 

Any data we can derive from there (e.g., sorted lists, slices of lists, or any other transformed versions of data we already have in the store) should not be saved in Redux, but be returned and transformed in selectors. 

This not only gives us reusable methods to get specific formats of data, it also makes our code cleaner, as we have all actions related to data in the store in one place.

Let’s say we want to display a list of all movies, mark the ones we have marked as owned in our user profile, and order them by release date.

For the selectedMovies, we still have our selector from the last Code Block. For the whole list of movies, we create a similar selector from the api, just for another api. 

I will just display the selector itself in the following example. Then we can create a new selector that gets all movies, adds the ‘owned’ field to it from the selectedUserMovies, and sorts it by release date:

const selectedUserMovies = useAppSelector(selectUserMovies(userId))

const selectedMovies = useAppSelector(selectMovies())

export const selectUserMovies = createSelector(

  [selectUserMovies, selectMovies],

  (userMovies, movies) => {

    return movies

      .map(movie => {

        const foundUserMovie = userMovies.find(m => m.id === movie.id);

        return { ...movie, owned: !!foundUserMovie };

      })

      .sort((a, b) => new Date(a.releaseDate).getTime() - new       

      Date(b.releaseDate).getTime());

  }

);

RTK Query in React: EndNote

This short blog post offered a first look at how to use Redux and Redux Toolkit Query better, using Selectors for API responses and logical changes to data already stored in the Redux Store. 

I hope it provided some new ideas on how to optimize your React application. To learn more about how we develop and use the latest technologies at adjoe, feel free to look into the adjoe engineer blog.

Build products that move markets

Data Science & Analytics1

Your Skills Have a Place at adjoe

Find a Position