Dec 21, 2025

Redux Toolkit for React: Ditch Boilerplate and Build Your First Slice

Tags

Redux Toolkit is where Redux stops feeling “painful” and starts feeling fun. When Redux first clicked for me was the moment I deleted 50+ lines of boilerplate and replaced them with a tiny createSlice. In this part, you and I will move from “old-school Redux” to modern Redux Toolkit (RTK)—the version the official docs recommend for almost every new project now.


Why Redux Toolkit?


Classic Redux made you write:

  • Action type constants
  • Action creators
  • Big switch reducers
  • Manual immutability (return {...state, ...})
Redux Toolkit wraps all that into a few small helpers and encourages best practices by default:
  • configureStore → store setup + DevTools + middleware
  • createSlice → actions + reducer in one place
  • createAsyncThunk → async flows (we’ll use this in a later part)
If Redux ever felt “too much”, RTK is the cure.


Imagine a simple Redux slice as:


redux-slice


You focus on “what state changes” and RTK does the wiring.


Step 1: Install Redux Toolkit and React-Redux


If you haven’t already:


npm install @reduxjs/toolkit react-redux

This gives you everything needed for modern Redux in React.


Step 2: Create a Store with configureStore


Create a file: src/store.js


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

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

// Optional: you can log the initial state
console.log('Initial state:', store.getState());


What this does:

  • Combines all slices under reducer
  • Enables Redux DevTools automatically
  • Wires up sensible middleware (incl. for async later)
You’ll add more slices (e.g., usertodos) to this store as the app grows.


Step 3: Create Your First Slice with createSlice


Create: src/counterSlice.js


// src/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  value: 0,
  step: 1
};

const counterSlice = createSlice({
  name: 'counter',        // slice name
  initialState,
  reducers: {
    increment(state) {
      // Immer lets us "mutate" safely
      state.value += state.step;
    },
    decrement(state) {
      state.value -= state.step;
    },
    reset(state) {
      state.value = 0;
    },
    setStep(state, action) {
      state.step = action.payload;
    }
  }
});

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


Key things to notice:

  • No manual switch statement.
  • No return { ...state }—you can write “mutating” code, and RTK handles immutability under the hood.
  • Action creators are generated for you (increment()decrement(), etc.).
Mentally, you can picture this slice as a small box:

redux-slice1


Step 4: Wire Store to React with <Provider>


In src/index.js (or main.jsx):


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

const root = createRoot(document.getElementById('root'));

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

Now every component inside <App /> can talk to the Redux store using hooks.


Step 5: Use Redux Toolkit in a React Component


Let’s build a nice little counter UI: src/App.js


// src/App.js
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, reset, setStep } from './counterSlice';

function App() {
  const { value, step } = useSelector(state => state.counter);
  const dispatch = useDispatch();

  return (
    <div style={{ padding: '1rem', fontFamily: 'sans-serif' }}>
      <h1>Redux Toolkit Counter</h1>
      <p>Current value: {value}</p>

      <label>
        Step:
        <input
          type="number"
          value={step}
          onChange={e => dispatch(setStep(Number(e.target.value) || 1))}
          style={{ marginLeft: '0.5rem', width: '4rem' }}
        />
      </label>

      <div style={{ marginTop: '1rem' }}>
        <button onClick={() => dispatch(decrement())}>-</button>
        <button onClick={() => dispatch(reset())} style={{ margin: '0 0.5rem' }}>
          Reset
        </button>
        <button onClick={() => dispatch(increment())}>+</button>
      </div>
    </div>
  );
}
export default App;


How it flows conceptually:

User clicks "+" → dispatch(increment()) → counterSlice.reducer runs "increment" → state.counter.value changes → useSelector sees new state → React re-renders App with updated value


This is the same Redux flow, just with far less boilerplate.
You can visualize it as:

redux-full-flow

Why Redux Toolkit Is a Big Deal (Compared to Classic Redux)


In classic Redux, the same counter would require:

  • Constants: INCREMENTDECREMENTRESET
  • Action creators: increment()decrement()reset()
  • Reducer with switch and manual immutability
  • Manual root reducer + store setup

With RTK:
  • One slice file
  • One store file
  • Hooks in components

That’s it. It’s easier to teach, easier to test, and much cleaner to maintain.


What’s Next in the Series


In Part 3, we’ll go deeper into connecting multiple slices, structuring larger apps, and making sure your React components stay clean and maintainable as the Redux store grows.

We’ll also touch on:

  • Splitting Redux logic by feature (auth, todos, cart)
  • Using selectors for derived data
  • When to keep state in Redux vs local useState


EmoticonEmoticon