avatarSaurabh Shah

Summary

The web content provides a comprehensive guide on integrating Redux Toolkit (RTK) into React and React Native applications, emphasizing its simplicity and benefits over traditional Redux, including handling asynchronous actions and API integration.

Abstract

The article, last updated on October 2, 2021, offers an in-depth tutorial on using Redux Toolkit (RTK) in React and React Native applications. It highlights RTK's ability to simplify Redux implementation by co-locating reducers, actions, and state in a single file, eliminating the need for action type constants, and providing out-of-the-box support for middleware like Thunk and Redux DevTools. The guide covers RTK integration steps, the concept of slices, and how to handle side-effects, including asynchronous requests and REST API interactions. It also discusses the use of createAsyncThunk for managing API call states and provides a demo with a CodeSandbox example. The author shares personal opinions on best practices, favoring traditional thunk functions over createAsyncThunk for clarity and simplicity.

Opinions

  • The author advocates for the use of Redux Toolkit over traditional Redux due to its simplified approach and reduced boilerplate.
  • Slices are praised for their cleanliness and ease of maintenance, as they encapsulate all related reducers, actions, and state.
  • The author suggests that local component state should be used when possible instead of Redux for simplicity.
  • While acknowledging RTK's built-in support for Thunk middleware, the author prefers using traditional thunk functions for side-effects and asynchronous logic.
  • The author finds createAsyncThunk to be complex and suggests it should only be used when status tracking for async operations is necessary, otherwise, traditional thunks are recommended.
  • Redux-persist is recommended as an essential tool for persisting state across sessions in React and React Native applications.
  • The author encourages readers to support their work by buying them a coffee, indicating a preference for direct contributions over other forms of remuneration.

Redux Toolkit RTK in react js web and react-native applications — Simplify redux with react

Last updated: Oct 02, 2021.

Photo by Brett Jordan on Unsplash

Redux ToolKit (RTK) (RTK implementation and async thunk with RTK) Please buy me a coffee if this is helpful buy me a coffee.

Why RTK (Redux Toolkit)?

  • Simplifies the redux implementation.
  • All feature related reducers, actions, state at one place in one file making it easy to understand and maintain.
  • No action.type constants required anymore (managed internally)
  • OOTB support for Thunk & Redux Dev Tools.

What else do you want? 😃

Why this story on RTK?

  • This is especially to understand how to handle side-effects, apis, async logic in actions & for createAsyncThunk.

SUMMARY / INDEX:

  1. Redux Toolkit integration.
  2. What is Slice?
  3. How to create a new state in store?
  4. Async requests / REST APIs within actions. (Inspiration for this story)
  5. Demo
  6. Opinion & guidelines.
  7. Redux-persist & Redux-toolkit implementation with multiple reduces, combine reducers made easy for React Native and React JS. React redux persist | Medium

Redux Toolkit integration:

We will cover how to introduce RTK in an existing react js or react native app. That’s right! Process is exactly the same for both.

No rocket science, simply let’s follow the steps on official guide (https://redux-toolkit.js.org/tutorials/quick-start):

  1. Assuming you already have a working react app.
  2. Install dependencies: npm install @reduxjs/toolkit react-redux
  3. You do not need to install redux, thunk separately while using redux toolkit.
  4. Here on, the coding changes start and it’s a 5 step process.
  5. Create a Redux Store.
  6. Provide the Redux Store to React app
  7. Create a Redux State Slice
  8. Add Slice Reducers to the Store
  9. Use Redux State and Actions in React Components

Voila! It’s done.

Working sandbox example: https://codesandbox.io/s/github/reduxjs/redux-essentials-counter-example/tree/master/

Sounds simple. Let’s take a deep dive.

A very simple step wise official guide is found on a single page at: Usage Guide | Redux Toolkit (redux-toolkit.js.org).

We will use the same to keep the understanding consistent.

What is a Slice:

  • A slice is created using ‘createSlice’ API from redux-toolkit.
  • It requires a string name to identify the slice, an initial state value, and one or more reducer functions at minimum.
  • Unlike traditional redux, slice reducers allow us to mutate the original state. It uses immer library internally to make this possible.
  • Clean: Code is so clean and you have reducers, actions, constants everything at one place to maintain.
  • You can create as many feature slices as required.
  • Make sure, create redux states wisely. Local component state is always the best choice.

EXAMPLE OF A COUNTER SLICE:

import { createSlice } from '@reduxjs/toolkit'
const initialState = {
  value: 0,
}
export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})
// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

How to create a new state in store?

  • By new state, I mean a state.counter, state.employee, state.config params that we wish to create in store as per the application requirement.
  • Every feature that requires data sharing or props drilling, must use redux. For simplicity, we create individual feature slices and they will be registered in our redux store.
  • Thus, each feature will have its own state making it easy for maintenance.

EXAMPLE OF A STORE:

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export default configureStore({
  reducer: {
    counter: counterReducer,
  },
})

Async requests / REST APIs / side-effects in actions:

  • This is the most critical and interesting part.
  • Majority of the user applications will require API/SDK integration to fetch remote data asynchronously. (REST APIs, Firebase SDK, async processing, other side-effects)
  • RTK sets up the thunk middleware by default. (https://redux-toolkit.js.org/usage/usage-guide#using-middleware-to-enable-async-logic)
  • There are 2 ways to fetch data asynchronously:
  • A typical slice file can include thunks and dispatch action creators defined in slice.reducers without any extra configuration.
  • Using createAsyncThunk API from redux toolkit.
  • ACCORDING TO ME, one should always use a typical thunk function for side-effects.
  • Only reason to use createAsyncThunk can be when “pending/loading”, “success” & “error” states of a thunk are required to make store updates.
  • I strongly recommend to do that also manually in “slice > reducers” if you feel any complexity with createAsyncThunk.

Demo:

https://codesandbox.io/s/github/reduxjs/redux-essentials-counter-example/tree/master

Opinion & guidelines:

Async request / Side-effects / REST APIs:

Thunk — traditional function

  • Thunk being the traditional thunk action returning a function with dispatch param is easy to use and requires no new learning.
  • It can be used as is even with redux toolkit.
  • Thunk functions need to be at the end of Slice, as the required actions need to be exported before using them in thunk.

Example of a Thunk:

// First, define the reducer and action creators via `createSlice`
const usersSlice = createSlice({
  name: 'users',
  initialState: {
    loading: 'idle',
    users: [],
  },
  reducers: {
    usersLoading(state, action) {
      // Use a "state machine" approach for loading state instead of booleans
      if (state.loading === 'idle') {
        state.loading = 'pending'
      }
    },
    usersReceived(state, action) {
      if (state.loading === 'pending') {
        state.loading = 'idle'
        state.users = action.payload
      }
    },
  },
})
// Destructure and export the plain action creators
export const { usersLoading, usersReceived } = usersSlice.actions
// Define a thunk that dispatches those action creators
const fetchUsers = () => async (dispatch) => {
  dispatch(usersLoading())
  const response = await usersAPI.fetchAll()
  dispatch(usersReceived(response.data))
}

createAsyncThunk:

  • createAsyncThunk should be used only for getting the real time status of the async function from one of these 3 options: “pending”, “fulfilled” or “rejected”.
  • Only advantage of using “createAsyncThunk” over traditional Thunk is that you can skip writing 3 lines of code dispatching pending, success & error actions.
  • Honestly, I felt createAsyncThunk is very confusing and difficult to learn.
  • It adds less value and comparatively creates more complexity. (Totally a personal opinion. If the feature is made, there must be a thought process and a kind of requirement that I might not have come across.)
  • Thus, if you are in the same boat facing difficulty in understanding createAsyncThunk, NO WORRIES! You can simply skip and continue using traditional Thunk action functions.
  • Although, no harm in still understanding the working of createAsyncThunk:

EXAMPLE OF createAsyncThunk:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: (builder) => {
    // Add reducers for additional action types here, and handle loading state as needed
    builder.addCase(fetchUserById.fulfilled, (state, action) => {
      // Add user to the state array
      state.entities.push(action.payload)
    })
  },
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))

How it works:

  • First param of createAsyncThunk is “string”. It can be anything that makes it easy for you to understand.
  • This string is used to create loading, success & error action.types behind the scenes. Most of the time, you will never require to use them as you are not going to dispatch it manually ever. Do not waste more time understanding more about this parameter (I did).
  • Second param is an async function or a promise that resolves asynchronously.
  • extraReducers: This is a new property required under reducers to handle createAsyncThunk functions.
  • All the reducers (switch cases) related to pending, fulfilled & rejected statuses of the createAsyncThunk are to be defined inside extraReducers. If you skip any of the status handling (like in the example), there is nothing wrong in it. They are optional.
  • Yet for detailed understanding from official documentation, check out the API reference at: createAsyncThunk | Redux Toolkit (redux-toolkit.js.org)

Redux-persist with Redux-toolkit:

Redux-persist is a must have implementation to persist required data across the user/browser/app sessions in a react JS or react-native app.

Redux-persist & Redux-toolkit implementation with multiple reduces, combine reducers made easy for React Native and React JS. React redux persist | Medium

I really hope this was helpful and you could buy me a coffee.

My other stories:

😀Thank you😀 😀Clapping is motivating 😀Do not hesitate to applause

React
Redux
Redux Toolkit
React Native
Reactjs
Recommended from ReadMedium