Which Is Better: Context API or Redux Slices, and RTK Query?

Paweł Pawłowski

31 August 2022, 7 min read

thumbnail post

What's inside

  1. When Redux Should be Used
  2. Slices in Redux Toolkit & Inject Reducer
  3. RTK Query
  4. Summary

Some people believe that Redux is over and shouldn’t be used anymore in any kind of project.

We have React-Query, Mobx, XState, Zustand, Recoil, and other state management libraries.

Read our Complete Guide to state management in React - part 1 and part 2.

However, recently Redux Toolkit, like the main library for Redux now, introduced a few interesting APIs. We will look at it and decide if using it over the Context API makes more sense.

Many people believe Context is a state management tool.

Some people wonder if we should be teaching (or learning) Redux in 2022, and according to Redux maintainer Mark Eriksen,

(Context), It's a Dependency Injection mechanism, whose only purpose is to make a single value accessible to a nested tree of React components. It's up to you to decide what that value is and how it's created. Typically, that's done using data from the React component state, i.e., useState and useReducer. So, you're doing all the "state management" yourself - Context just gives you a way to pass it down the tree.

Redux is a library and a pattern for separating your state update logic from the rest of your app and making it easy to trace when,where,why and how your state has changed. It also allows your whole app to access any piece of state in any component.

In addition, there are some distinct differences between how Context and (React-) Redux pass along updates. Context has some major limitations - in particular, any component that consumes a context will be forced to re-render, even if it only cares about part of the context value.

The whole discussion and Mark Eriksen’s answer are available here.

When Redux Should be Used

According to Redux documentation, you might need Redux when:

  • You have large amounts of application states needed in many places in the app.
  • The app state is updated frequently.
  • The logic for updating that state may be complex.
  • The app has a medium or large-sized codebase and might be worked on by many people.
  • You need to see how that state is being updated over time.

Slices in Redux Toolkit & Inject Reducer

Redux Toolkit now offer a slice API.

You can create actions and reducers in one file:

Example from Redux Toolkit documentation.

import { createSlice } from '@reduxjs/toolkit' 
import type { PayloadAction } from '@reduxjs/toolkit' 

interface CounterState { 
  value: number 
} 

const initialState = { value: 0 } as CounterState 

const counterSlice = createSlice({ 
  name: 'counter', 
  initialState, 
  reducers: { 
    increment(state) { 
      state.value++ 
    }, 
    decrement(state) {
      state.value-- 
    }, 
    incrementByAmount(state, action: PayloadAction<number>) { 
      state.value += action.payload 
    }, 
  }, 
}) 

export const { increment, decrement, incrementByAmount } = counterSlice.actions 
export default counterSlice.reducer 

Also, there is an option to inject reducers:

import { createStore } from 'redux' 

// Define the Reducers that will always be present in the application 
const staticReducers = { 
  users: usersReducer, 
  posts: postsReducer 
} 

// Configure the store 
export default function configureStore(initialState) { 
  const store = createStore(createReducer(), initialState) 

  // Add a dictionary to keep track of the registered async reducers 
  store.asyncReducers = {} 

  // Create an inject reducer function 
  // This function adds the async reducer, and creates a new combined reducer 
  store.injectReducer = (key, asyncReducer) => { 
    store.asyncReducers[key] = asyncReducer 
    store.replaceReducer(createReducer(store.asyncReducers)) 
  } 

  // Return the modified store 
  return store 
} 

function createReducer(asyncReducers) { 
  return combineReducers({ 
    ...staticReducers, 
    ...asyncReducers 
  }) 
} 

With this not only can you make your application more performance-oriented, and inject only part of the store you need for a particular view. Also, you have Redux developer tools available which are helpful when working on bigger, scalable applications.

There are also a lot of packages that can help you with that.

RTK Query

On top of that if you use Redux Toolkit slice API you can also migrate to RTK Query which uses slices API.

RTK Query solves different kinds of problems than Redux:

  • Tracking loading state to show UI spinners.
  • Avoiding duplicate requests for the same data.
  • Optimistic updates to make the UI feel faster.
  • Managing cache lifetimes as the user interacts with the UI.

There are two ways of initializing it:

import { createApi } from '@reduxjs/toolkit/query' 

/* React-specific entry point that automatically generates 
   hooks corresponding to the defined endpoints */ 
import { createApi } from '@reduxjs/toolkit/query/react' 

and then you can write base hook, that you will re-use later on:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' 
import type { Pokemon } from './types' 

// Define a service using a base URL and expected endpoints 
export const pokemonApi = createApi({ 
  reducerPath: 'pokemonApi', 
  baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), 
  endpoints: (builder) => ({ 
    getPokemonByName: builder.query<Pokemon, string>({ 
      query: (name) => `pokemon/${name}`, 
    }), 
  }), 
}) 

// Export hooks for usage in functional components, which are 
// auto-generated based on the defined endpoints 
export const { useGetPokemonByNameQuery } = pokemonApi

You will also need to configure the store as it works with Redux:

import { configureStore } from '@reduxjs/toolkit' 
// Or from '@reduxjs/toolkit/query/react' 
import { setupListeners } from '@reduxjs/toolkit/query' 
import { pokemonApi } from './services/pokemon' 

export const store = configureStore({ 
  reducer: { 
    // Add the generated reducer as a specific top-level slice 
    [pokemonApi.reducerPath]: pokemonApi.reducer, 
  }, 
  // Adding the api middleware enables caching, invalidation, polling, 
  // and other useful features of `rtk-query`. 
  middleware: (getDefaultMiddleware) => 
    getDefaultMiddleware().concat(pokemonApi.middleware), 
}) 

// optional, but required for refetchOnFocus/refetchOnReconnect behaviors 
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization 
setupListeners(store.dispatch) 

And now you have cashing included, and the same goodies as React Query. You can use now use the hook we prepared before:

Below example from RTK Query documentation.

import * as React from 'react' 
import { useGetPokemonByNameQuery } from './services/pokemon' 

export default function App() { 
  // Using a query hook automatically fetches data and returns query values 
  const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur') 
  // Individual hooks are also accessible under the generated endpoints: 
  // const { data, error, isLoading } = pokemonApi.endpoints.getPokemonByName.useQuery('bulbasaur') 

  // render UI based on data and loading state 
} 

As you can see the API is quite similar to React Query. If you are interested about the differences between those two, there is an interesting table in React Query documentation - check it here.

There is also some comparison in Redux Toolkit documentation which concentrates on different aspects of the comparison - here.

Summary

There is no one-size-fits-all solution in the ongoing debate between Context API and Redux Toolkit, including Redux Slices and RTK Query. The choice between them depends on the specific project requirements and the comfort level of the development team. While some argue that Redux is no longer necessary in the face of newer state management libraries like React-Query or Mobx, Redux Toolkit introduces compelling features that may sway the decision.

The introduction of Redux Toolkit's slice API simplifies the creation of actions and reducers, providing a cleaner and more organized code structure. Additionally, injecting reducers allows for a more performance-oriented approach, loading only the necessary parts of the store for a particular view. The integration with RTK Quer y further enhances Redux Toolkit's capabilities, addressing issues such as loading state tracking, duplicate requests, optimistic updates, and cache management.

Ultimately, Redux Toolkit and Context serve different purposes, and the choice between them should be based on the project's complexity and the development team's expertise. Exploring Redux Toolkit and RTK Query could be a valuable consideration for seasoned professionals comfortable with Redux. On the other hand, less experienced developers might find Context and React Query to be more accessible alternatives.

For tailored guidance on choosing the right state management solution for your project, feel free to reach out to our team at Sunscrapers. Contact us today to discuss your requirements and leverage our expertise in building scalable and efficient React applications.

Share

Recent posts

See all blog posts

Are you ready for your next project?

Whether you need a full product, consulting, tech investment or an extended team, our experts will help you find the best solutions.