Redux Toolkit – React

Redux Toolkit (RTK) is the official, recommended way to write Redux logic. It was introduced to simplify common Redux tasks, reduce boilerplate, and enforce best practices. It abstracts away much of the boilerplate associated with configuring Redux, including setting up the store, writing reducers, and handling asynchronous logic (via createAsyncThunk).

Why Use Redux Toolkit?

  • Simplifies Redux setup: Less configuration and boilerplate.
  • Immutability and immutability safety: Uses Immer.js under the hood, allowing for safe, immutable updates while writing “mutable” code.
  • Handles side effects: Comes with utilities like createAsyncThunk to handle async logic.
  • Provides best practices: Encourages slice-based state management.

Key Concepts in Redux Toolkit

  1. configureStore(): Sets up the Redux store with good defaults (like combining reducers, adding middleware).
  2. createSlice(): Automatically generates action creators and action types corresponding to the reducers and state you define.
  3. createAsyncThunk(): Simplifies handling asynchronous logic (like API calls).
  4. createReducer(): Provides a flexible way to define reducers that respond to actions.

Basic Redux Toolkit Example

Step 1: Installing Redux Toolkit

npm install @reduxjs/toolkit react-redux

Step 2: Creating a Slice

A slice combines your reducer logic and actions for a specific part of your Redux state.

import { createSlice } from '@reduxjs/toolkit';

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

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

Explanation:

  • createSlice generates the Redux slice, including the reducer and actions automatically.
  • increment, decrement, and incrementByAmount are the action creators.
  • counterSlice.reducer is the reducer function, which we’ll use to configure the store.

Step 3: Configuring the Store

Use configureStore to set up the store with slices.

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;

Step 4: Using Redux State and Actions in a Component

You can now access the Redux state and dispatch actions in your React components using useSelector and useDispatch from react-redux.

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <button onClick={() => dispatch(incrementByAmount(10))}>Increment by 10</button>
    </div>
  );
}

export default Counter;
  • useSelector is used to extract data from the Redux store.
  • useDispatch is used to dispatch actions like increment and decrement.

Step 5: Providing the Store to the React App

Wrap the root component of your app with the Provider component from react-redux to give components access to the Redux store.

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './Counter';

ReactDOM.render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);

Handling Asynchronous Logic with createAsyncThunk

For handling asynchronous logic like API calls, Redux Toolkit provides createAsyncThunk, which automatically handles the lifecycle of the async action (e.g., loading, success, and failure states).

Example: Fetching Data with createAsyncThunk

Let’s create a simple app that fetches data from an API using createAsyncThunk.

Step 1: Define an Async Thunk

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// Define the async thunk for fetching data
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  return response.json();
});

const postsSlice = createSlice({
  name: 'posts',
  initialState: {
    posts: [],
    loading: 'idle', // 'idle' | 'pending' | 'succeeded' | 'failed'
    error: null,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending, (state) => {
        state.loading = 'pending';
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.loading = 'succeeded';
        state.posts = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.loading = 'failed';
        state.error = action.error.message;
      });
  },
});

export default postsSlice.reducer;
  • createAsyncThunk generates three action types: pending, fulfilled, and rejected.
  • The extraReducers field is used to handle actions generated by createAsyncThunk (like loading, success, and error states).

Step 2: Configuring the Store

import { configureStore } from '@reduxjs/toolkit';
import postsReducer from './postsSlice';

const store = configureStore({
  reducer: {
    posts: postsReducer,
  },
});

export default store;

Step 3: Dispatching the Thunk in a Component

You can now dispatch the fetchPosts async action and display the fetched data in your component.

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchPosts } from './postsSlice';

function PostsList() {
  const dispatch = useDispatch();
  const posts = useSelector((state) => state.posts.posts);
  const loading = useSelector((state) => state.posts.loading);
  const error = useSelector((state) => state.posts.error);

  useEffect(() => {
    dispatch(fetchPosts());
  }, [dispatch]);

  if (loading === 'pending') {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error}</div>;
  }

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default PostsList;

Example Breakdown:

  1. Async Thunk: fetchPosts is dispatched on component mount to trigger the API call.
  2. Loading and Error Handling: The loading and error state is managed by Redux Toolkit’s extraReducers.
  3. Rendering the Fetched Data: Once the data is successfully fetched, it is displayed in the component.

Summary of Core Features

  1. configureStore():
  • Automatically sets up the store with default middleware (e.g., Redux DevTools, thunk middleware).
  • Combines reducers and applies middleware.
  1. createSlice():
  • A more convenient way to define reducers and action creators in one step.
  • Automatically generates actions based on the reducer functions.
  1. createAsyncThunk():
  • Simplifies the handling of asynchronous logic like API requests.
  • Generates actions for the three lifecycle states of a promise (pending, fulfilled, rejected).
  1. createReducer():
  • A flexible reducer creator that allows for both object notation and switch-case handling.
  1. Middleware and DevTools:
  • configureStore enables Redux DevTools and middleware automatically, which provides a great development experience out of the box.

Why Redux Toolkit is Better for Modern Redux Development

  • Less Boilerplate: Writing reducers, actions, and setting up middleware is much simpler.
  • Immutable State Handling: Uses Immer under the hood, so you can “mutate” state directly in reducers without actually mutating it.
  • Built-in Async Support: createAsyncThunk makes it easier to manage async actions like API calls.
  • Better DevTools Integration: Redux Toolkit automatically sets up the Redux DevTools extension.
  • Encourages Best Practices: By default, RTK encourages slice-based architecture, proper store setup, and separation of concerns.

In summary, Redux Toolkit is the preferred way to work with Redux due to its simplicity, reduced boilerplate, and out-of-the-box best practices. It drastically improves the developer experience by making state management in React more efficient and scalable.

Tags: No tags

Add a Comment

Your email address will not be published. Required fields are marked *