Home > Web Front-end > JS Tutorial > Comprehensive Redux Toolkit Notes for React Developers

Comprehensive Redux Toolkit Notes for React Developers

Barbara Streisand
Release: 2025-01-15 07:37:43
Original
271 people have browsed it

Comprehensive Redux Toolkit Notes for React Developers

? Redux Toolkit Notes ?

What is Redux?
Redux is a flexible state container for JS apps that manages our application state separately. It manages the application state in a single store, making it easier to handle complex state logic across the entire app.

Why Redux?
In normal flow, we need to do prop drilling to pass states in between components. Some levels don’t need the states here, which is a burden. Also uplifting a state for large medium apps isn’t a scalable solution as it requires structural changes. That’s why we need redux to manage states. All the states here are kept in store and whichever component needs that they can just subscribe to that store. Redux ensures predictable state management, easier debugging, and improved scalability by enforcing a unidirectional data flow.

Core Redux Components:

Action: An object that describes what happened. It typically contains a type and an optional payload. (A command)
Dispatch: A function used to send actions to the store to update the state. (A event occurring)
Reducer: A pure function that takes the current state and an action, then returns a new state. (Function that triggers when action dispatched)

Installing: npm i @reduxjs/toolkit react-redux

Redux Workflow:

Creating a Slice:
A slice is a collection of Redux reducer logic and actions for a single feature. The prepare callback allows US to customize the action payload before it reaches the reducer.

import { createSlice, nanoid } from "@reduxjs/toolkit";

const postSlice = createSlice({
 name: "posts",
 initialState: [],
 reducers: {
   addPost: {
     reducer: (state, action) => {
       state.push(action.payload);
     },
     prepare: (title, content) => ({
       payload: { id: nanoid(), title, content },
     }),
   },
   deletePost: (state, action) => {
     return state.filter((post) => post.id != action.payload);
   },
 },
});

export const { addPost, deletePost } = postSlice.actions;

export default postSlice.reducer;
Copy after login
Copy after login

Creating store:

import { configureStore } from "@reduxjs/toolkit";
import postReducer from "../features/posts/postSlice";

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

Copy after login
Copy after login

Wrap with provider:

import { Provider } from "react-redux";
import { store } from "./app/store.jsx";

createRoot(document.getElementById("root")).render(
 <StrictMode>
   <Provider store={store}>
     <App />
   </Provider>
 </StrictMode>
);
Copy after login
Copy after login

Use in Component:

const PostList = ({ onEdit }) => {
 const posts = useSelector((state) => state.posts);
 const dispatch = useDispatch();

 return (
   <div className="w-full grid grid-cols-1 gap-6 mt-12">
     {posts.map((post) => (
       <div key={post.id}></div>
     ))}
   </div>
 );
};
Copy after login
Copy after login

Redux Browser Extension: Redux DevTools

const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== 'production',
});
Copy after login
Copy after login

Async Operation in Redux (Redux Thunk):

In Redux, asynchronous operations (like API calls) are handled using middleware because Redux by default only supports synchronous state updates. The most common middlewares for handling async operations are Redux Thunk, Redux Toolkit (RTK) with createAsyncThunk, and Redux Saga.

Implementation:

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

// Fetch all posts
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  return response.json();
});

// Initial State
const initialState = {
  posts: [],
  post: null,
  loading: false,
  error: null,
};

// Slice
const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      // Fetch all posts
      .addCase(fetchPosts.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.loading = false;
        state.posts = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      })

      },
});

export default postsSlice.reducer;
Copy after login
Copy after login

Use Case:

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

const Posts = () => {
  const dispatch = useDispatch();
  const { posts, loading, error } = useSelector((state) =>state.posts);

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

  const handleCreate = () => {
    dispatch(createPost({ title: 'New Post', body: 'This is a new post' }));
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Posts</h1>
      <button onClick={handleCreate}>Create Post</button>
     </div>
  );
};

export default Posts;
Copy after login
Copy after login

Middleware
Middleware in Redux intercepts dispatched actions, allowing for logging, crash reporting, or handling async logic. Middleware lets us customize the dispatch process.

const blogPostMiddleware = (storeAPI) => (next) => (action) => {
  if (action.type === 'posts/publishPost') {
    const contentLength = action.payload.content.length;

    if (contentLength < 50) {
      console.warn('Post content is too short. Must be at least 50 characters.');
      return;
    }
    console.log('Publishing post:', action.payload.title);
  }
  return next(action);
};

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(blogPostMiddleware),
});
Copy after login
Copy after login

Selectors
Selectors help access specific parts of the state.

export const selectCount = (state) => state.counter.value;

Error Handling
Handle errors effectively with proper state management.

import { createSlice, nanoid } from "@reduxjs/toolkit";

const postSlice = createSlice({
 name: "posts",
 initialState: [],
 reducers: {
   addPost: {
     reducer: (state, action) => {
       state.push(action.payload);
     },
     prepare: (title, content) => ({
       payload: { id: nanoid(), title, content },
     }),
   },
   deletePost: (state, action) => {
     return state.filter((post) => post.id != action.payload);
   },
 },
});

export const { addPost, deletePost } = postSlice.actions;

export default postSlice.reducer;
Copy after login
Copy after login

RTK Query (Simplified Data Fetching)

RTK Query simplifies data fetching, caching, and synchronization. RTK Query automatically caches requests and avoids unnecessary refetching, improving performance.

Setting Up RTK Query

import { configureStore } from "@reduxjs/toolkit";
import postReducer from "../features/posts/postSlice";

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

Copy after login
Copy after login

Usage in Components

import { Provider } from "react-redux";
import { store } from "./app/store.jsx";

createRoot(document.getElementById("root")).render(
 <StrictMode>
   <Provider store={store}>
     <App />
   </Provider>
 </StrictMode>
);
Copy after login
Copy after login

Immutable Updates with Immer

Immer allows us to write logic that "mutates" state directly while keeping the updates immutable under the hood.

const PostList = ({ onEdit }) => {
 const posts = useSelector((state) => state.posts);
 const dispatch = useDispatch();

 return (
   <div className="w-full grid grid-cols-1 gap-6 mt-12">
     {posts.map((post) => (
       <div key={post.id}></div>
     ))}
   </div>
 );
};
Copy after login
Copy after login

Mutate vs. Immutable

Mutate: Changing the data directly. For example, modifying an object or array.
Immutable: Instead of modifying data directly, we create a new copy with the changes applied, leaving the original data untouched.

How Immer Works
Immer helps us write code that looks like we're mutating data (i.e., changing it directly), but it automatically keeps the changes immutable under the hood. This is useful for avoiding common bugs when dealing with immutable data structures in JavaScript.
Example: Without Immer (mutation):

const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== 'production',
});
Copy after login
Copy after login

With Immer (immutability):

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

// Fetch all posts
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  return response.json();
});

// Initial State
const initialState = {
  posts: [],
  post: null,
  loading: false,
  error: null,
};

// Slice
const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      // Fetch all posts
      .addCase(fetchPosts.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.loading = false;
        state.posts = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      })

      },
});

export default postsSlice.reducer;
Copy after login
Copy after login

This makes working with Redux (or any state management) easier because we don’t have to clone and update the state manually; Immer does it for us automatically.

Redux Persist:

To persist Redux state across page refreshes, we can integrate Redux Persist. This will store your Redux state in local storage or session storage and reload it when the app is refreshed.

Install:
npm install redux-persist

Implement:

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

const Posts = () => {
  const dispatch = useDispatch();
  const { posts, loading, error } = useSelector((state) =>state.posts);

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

  const handleCreate = () => {
    dispatch(createPost({ title: 'New Post', body: 'This is a new post' }));
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Posts</h1>
      <button onClick={handleCreate}>Create Post</button>
     </div>
  );
};

export default Posts;
Copy after login
Copy after login

Wrap with Persisit Gate:

const blogPostMiddleware = (storeAPI) => (next) => (action) => {
  if (action.type === 'posts/publishPost') {
    const contentLength = action.payload.content.length;

    if (contentLength < 50) {
      console.warn('Post content is too short. Must be at least 50 characters.');
      return;
    }
    console.log('Publishing post:', action.payload.title);
  }
  return next(action);
};

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(blogPostMiddleware),
});
Copy after login
Copy after login

Optional Enhancements

Use sessionStorage Instead of localStorage:
Change the storage to session-based (clears when the browser closes):

initialState: {
  items: [],
  status: 'idle',
  error: null,
},

.addCase(fetchData.rejected, (state, action) => {
  state.status = 'failed';
  state.error = action.error.message;
});
Copy after login

Selective Persistence:
Only persist specific slices of the state:

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

const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com' }),
  endpoints: (builder) => ({
    getPosts: builder.query({
      query: () => '/posts',
    }),
    getPostById: builder.query({
      query: (id) => `/posts/${id}`,
    }),
    createPost: builder.mutation({
      query: (newPost) => ({
        url: '/posts',
        method: 'POST',
        body: newPost,
      }),
    }),
    updatePost: builder.mutation({
      query: ({ id, ...updatedPost }) => ({
        url: `/posts/${id}`,
        method: 'PUT',
        body: updatedPost,
      }),
    }),
    deletePost: builder.mutation({
      query: (id) => ({
        url: `/posts/${id}`,
        method: 'DELETE',
      }),
    }),
  }),
});

export const {
  useGetPostsQuery,
  useGetPostByIdQuery,
  useCreatePostMutation,
  useUpdatePostMutation,
  useDeletePostMutation,
} = api;
export default api;
Copy after login

I have created a simple blog project with react, redux and ant design having CRUD functionality. You can check it out.
Project Link - Redux Blog App

? Master Redux Toolkit and elevate your React apps!

The above is the detailed content of Comprehensive Redux Toolkit Notes for React Developers. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template