Skip to content

Redux Toolkit Integration

Redux Toolkit Integration with Next.js

Redux Toolkit simplifies state management in enterprise applications by reducing boilerplate and enabling normalized state structures. When integrated with Next.js, it provides a scalable solution for managing global state across server and client components. This section covers slice creation, middleware configuration, and integration patterns for Next.js projects.


Setting Up Redux Toolkit in a Next.js Project

  1. Install Dependencies
    Ensure @reduxjs/toolkit and react-redux are installed:

    npm install @reduxjs/toolkit react-redux
    

  2. Create the Redux Store
    Generate a store.js file in a store directory (e.g., store/index.js):

    // store/index.js
    import { configureStore } from '@reduxjs/toolkit';
    import userSlice from './features/userSlice';
    
    const store = configureStore({
      reducer: {
        user: userSlice,
      },
    });
    
    export default store;
    

  3. Wrap Next.js App with Provider
    In pages/_app.js (or app/layout.tsx for App Directory), wrap your app with Provider:

    // pages/_app.js
    import { Provider } from 'react-redux';
    import store from '../store';
    
    function MyApp({ Component, pageProps }) {
      return (
        <Provider store={store}>
          <Component {...pageProps} />
        </Provider>
      );
    }
    
    export default MyApp;
    


Creating Redux Slices

Slices encapsulate state logic for specific features. Use createSlice to define reducers and actions:

// store/features/userSlice.js
import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    loading: false,
    error: null,
  },
  reducers: {
    setUser(state, action) {
      state.data = action.payload;
    },
    setLoading(state, action) {
      state.loading = action.payload;
    },
    setError(state, action) {
      state.error = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  },
});

export const { setUser, setLoading, setError } = userSlice.actions;
export default userSlice.reducer;

Handling Async Actions with createAsyncThunk

Define async operations using createAsyncThunk and integrate them with your slices:

// store/features/userSlice.js
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId) => {
    const response = await axios.get(`/api/user/${userId}`);
    return response.data;
  }
);

Middleware and Async Flow

RTK's configureStore automatically includes the thunk middleware for handling async actions. For custom middleware (e.g., logging or API interceptors), add it via the middleware option:

const store = configureStore({
  reducer: {
    user: userSlice.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(loggerMiddleware),
});

Best Practices

  • Normalize State: Use RTK's built-in normalization to avoid nested objects.
  • Namespace Slices: Avoid global state for UI-specific data (e.g., use local state for dropdowns).
  • Use useSelector Efficiently: Memoize selectors to prevent unnecessary re-renders.
  • Leverage useDispatch: Dispatch actions using the hook for imperative updates.

Key takeaways

  • Use createSlice to define reducers and actions, and createAsyncThunk for async operations.
  • Configure the Redux store with configureStore and wrap Next.js apps with Provider for global state access.
  • Leverage RTK's built-in middleware for async flow and extend it with custom logic if needed.
  • Follow normalization and scoping best practices to maintain scalable, maintainable state structures.