REACT Redux Toolkit

Redux Toolkit (RTK) is the official, recommended way to write Redux logic. It simplifies Redux development by reducing boilerplate code and providing powerful utilities for state management.

Why Use Redux Toolkit?

  1. Less Boilerplate: RTK simplifies writing reducers, actions, and store configuration.
  2. Immutability with Immer: Allows writing “mutating” code, which is internally converted to immutable updates.
  3. Built-in Middleware: Includes Redux Thunk for handling asynchronous logic.
  4. Opinionated Defaults: Encourages best practices like slice-based state organization.
  5. Built-in DevTools Support: Works seamlessly with Redux DevTools.

How Redux Toolkit Works

The main tools in RTK are:

  1. configureStore: Simplifies store setup.
  2. createSlice: Combines reducers and actions in one step.
  3. createAsyncThunk: Handles asynchronous logic (like API calls).
  4. createSelector: For memoized state selection.

Setting Up Redux Toolkit in React

1. Install Redux Toolkit

npm install @reduxjs/toolkit react-redux

Try It Now

2. Create a Slice

A slice represents a piece of state and includes its actions and reducers.

 

3. Configure the Store

Use configureStore to set up the Redux store.

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

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

export default store;

Try It Now

4. Provide the Store to React

Wrap your application in the Redux Provider to make the store accessible to all components.

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

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

Try It Now

5. Access State and Dispatch Actions

Use useSelector to access state and useDispatch to dispatch actions in your components.

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

const Counter = () => {
  const count = useSelector((state) => state.counter.count); // Access state
  const dispatch = useDispatch(); // Dispatch actions

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <button onClick={() => dispatch(reset())}>Reset</button>
    </div>
  );
};

export default Counter;

Try It Now

Handling Async Logic with createAsyncThunk

createAsyncThunk simplifies working with async logic like API calls.

1. Define an Async Thunk

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

// Async thunk for fetching data
export const fetchUser = createAsyncThunk('user/fetchUser', async (userId) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
  return response.json();
});

// Create slice
const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  },
});

export default userSlice.reducer;

Try It Now

2. Use the Thunk in a Component

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

const User = () => {
  const dispatch = useDispatch();
  const user = useSelector((state) => state.user.data);
  const status = useSelector((state) => state.user.status);

  useEffect(() => {
    dispatch(fetchUser(1)); // Fetch user with ID 1
  }, [dispatch]);

  if (status === 'loading') return <p>Loading...</p>;
  if (status === 'failed') return <p>Error fetching user data.</p>;

  return (
    <div>
      {user && (
        <>
          <h1>{user.name}</h1>
          <p>{user.email}</p>
        </>
      )}
    </div>
  );
};

export default User;

Try It Now

Debugging with Redux DevTools

configureStore automatically enables Redux DevTools, making it easy to monitor state changes and dispatched actions.

Best Practices with Redux Toolkit

  1. Use Slices for Modular State: Create separate slices for each feature or domain.
  2. Avoid Nesting State: Keep your state as flat as possible for better performance.
  3. Use Middleware for Side Effects: Leverage createAsyncThunk or custom middleware for async logic.
  4. Memoize Selectors: Use createSelector from reselect to avoid unnecessary re-renders.
  5. Use DevTools: Monitor dispatched actions and state changes during development.