Introduction
Redux Toolkit (RTK) is the official, recommended way to write Redux logic.
It was designed to fix the long-standing complaints developers had about Redux:
- Redux was too “boilerplate-heavy”
- Setting up a store was complicated
- Writing reducers with switch statements felt repetitive
- Immutable updates were hard and easy to get wrong
- Common patterns (thunks, immutable updates, devtools) required many
separate libraries and configs
Redux Toolkit solves these by bundling the best Redux practices into one package with a simple API.
RTK is built around three ideas:
- Simplicity: Write less code.
- Predictability: Opinionated patterns to avoid mistakes.
- Power: Includes tools for async logic, immutable updates, and store
setup.
Before RTK, a typical Redux app required:
- action types
- action creators
- reducers with switch statements
- manual immutable updates
- configuring applyMiddleware
- installing thunk, devtools enhancer, combineReducers, etc.
This made Redux feel complicated for beginners.
Redux Toolkit reduces all that to:
- createSlice
- configureStore
- createAsyncThunk
- createReducer and createAction (lower-level APIs)
With RTK:
- reducers are auto-generated from slices
- immutable updates are done automatically using Immer
- store setup is one line
- async logic is simpler
- no need for switch statements or constants
In this article, we will learn a lot about Redux Toolkit by building a shopping cart. And to gain the most from this article, I have listed the prerequisites below in the next section.
Prerequisites
- NodeJS should be installed on your system
- Knowledge of React
- Knowledge of JavaScript
- Knowledge of Redux is not required, but it is an added advantage
Getting Started
To get started, clone the starter project by running the code below:
git clone https://github.com/lawrence-eagles/shopping-cart-starter shopping-cart
Then change directory by running:
cd shopping-cart
Now start the app by running:
npm run dev
And we get:

In this section, we will focus on building a shopping cart. And to do this, we first need to set up our store.
Redux store and Redux are kind of used interchangeably; both stand for a container for JavaScript apps. And this stores the whole state of the app in an immutable object tree.
To configure the Redux store, create an app directory inside the src directory. Then create a file named store.js inside the src directory. Now add the code below to the store.js file:
// app/store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
export const store = configureStore({
reducer: { },
});
After configuring the store, we need to provide the global state to our application.
Provide the Store to React
To provide the store to React, replace the code in the main.jsx with the following code:
import React from 'react';
import { createRoot } from 'react-dom/client';
import { store } from './store';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
In the code above, we used the Provider component to provide our store — our global state, to our application. Next, we create a slice.
Create a Slice
A slice in Redux Toolkit is a collection of Redux logic for a single feature of your application. It bundles state, reducers, and action creators into one cohesive unit.
Think of a slice as one feature = one slice.
In this section, we will create the cart slice and the modal slice for the cart feature and the modal feature. To do this, create a folder called features in the src directory and create a folder named cart inside the features directory. Next, create a file named cartSlice.js in the cart folder and add the following code to the cartSlice.js file:
import { createSlice } from "@reduxjs/toolkit";
import cartItems from "../../cartItems";
const initialState = {
cartItems,
amount: 0,
total: 0,
isLoading: false,
};
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
clearCart: (state) => {
state.cartItems = [];
},
removeItem: (state, action) => {
const itemId = action.payload;
state.cartItems = state.cartItems?.filter((item) => item.id !== itemId);
},
increase: (state, { payload }) => {
const cartItem = state.cartItems.find((item) => item.id === payload);
cartItem.amount += 1;
},
decrease: (state, { payload }) => {
const cartItem = state.cartItems.find((item) => item.id === payload);
cartItem.amount -= 1;
},
calculateTotals: (state) => {
let amount = 0;
let total = 0;
state.cartItems.forEach((item) => {
amount += item.amount;
total += item.amount * item.price;
});
state.amount = amount;
state.total = total;
},
},
});
// console.log("cartSlice", cartSlice)
export const { clearCart, removeItem, increase, decrease, calculateTotals } =
cartSlice.actions;
export default cartSlice.reducer;
In the code above, the initialState has four properties: the cartItems, which is the cartItems data, amount, which is 0, total, which is 0, and isLoading, which is false.
In the createSlice, we created five reducers to handle different actions. These reducers are clearCart, removeItem, increase, decrease, and calculateTotals. Then we exported five action creators, which are automatically created with the same name as the reducers provided. Redux Toolkit automatically creates these action creators for us.
Also, RTK uses Immer.js internally, allowing you to write “mutating” logic safely as seen in the reducers in the code above.
Finally, Redux Toolkit automatically generates a slice reducer, which is exported using the default export.
Next, in the features directory, create a folder called modal and inside the modal folder create a file called modalSlice.js and add the code below to the modalSlice.js file:
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isOpen: false,
}
const modalSlice = createSlice({
name: 'modal',
initialState,
reducers: {
openModal: (state, action) => {
state.isOpen = true;
},
closeModal: (state, action) => {
state.isOpen = false;
}
}
})
export const {openModal, closeModal} = modalSlice.actions;
export default modalSlice.reducer;
In the code above, the initialState has only the isOpen property, which is false. And in the createSlice function, we created two reducers: openModal and closeModal. Additionally, two action creators with the same names as these reducers are automatically created for us by Redux Toolkit, and they are exported.
Finally, a slice reducer is automatically created by Redux Toolkit, and this is exported using the default export.
Next, we need to add both slice reducers to the application store, and we will do this in the next section.
Update Store
To add the slice reducers to the application store, update the code in the store.js file with the following:
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./features/cart/cartSlice";
import modalReducer from "./features/modal/modalSlice";
export const store = configureStore({
reducer: {
cart: cartReducer,
modal: modalReducer,
},
})
Now both the cartReducer and the modalReducer are available to the entire app through the provider.
Use the Store in Components
To do this, add the following code to the App.jsx file:
import { useEffect } from "react";
import Navbar from "./components/Navbar";
import Modal from "./components/Modal";
import CartContainer from "./components/CartContainer";
import { useDispatch, useSelector } from "react-redux";
import { calculateTotals } from "./features/cart/cartSlice";
function App() {
const { cartItems, isLoading } = useSelector((store) => store.cart);
const { isOpen } = useSelector((store) => store.modal);
const dispatch = useDispatch();
useEffect(() => {
dispatch(calculateTotals());
}, [cartItems]);
return (
<>
{isLoading && (
<div className="loading">
<h1>Loading...</h1>
</div>
)}
<main>
{isOpen && !isLoading && <Modal />}
{!isLoading && (
<>
<Navbar />
<CartContainer />
</>
)}
</main>
</>
);
}
export default App;
In the code above, cartItems, isLoading, and isOpen are destructured from the store using the useSelector hook provided by react-redux.
Also, react-redux provides the useDispatch hook, which returns the store’s dispatch function. Additionally, calling dispatch(calculateTotals()) sends the action to the Redux store, and the slice reducer handles it and updates the state.
Next, we will create the Modal, the Navbar, and the CartContainer components.
To do this, in the src directory, create a folder named components and in the components folder, create a file named Modal.jsx and add the following code to the Modal.jsx file:
import React from 'react'
import { useDispatch } from 'react-redux'
import { clearCart } from '../features/cart/cartSlice'
import { closeModal } from '../features/modal/modalSlice'
const Modal = () => {
const dispatch = useDispatch()
return (
<aside className="modal-container">
<div className="modal">
<h4>remove all items from your shopping cart?</h4>
<div className="btn-container">
<button type="button" className='btn confirm-btn'
onClick={()=> {
dispatch(clearCart())
dispatch(closeModal())
}}>confirm</button>
<button type="button" className='btn clear-btn'
onClick={()=> dispatch(closeModal()) }>cancel</button>
</div>
</div>
</aside>
)
}
export default Modal
Next, create a file named Navbar.jsx inside the components folder that is in the src directory. And add the following code to the Navbar.jsx file:
import {CartIcon} from "../icons";
import { useSelector } from "react-redux";
const Navbar = () => {
const {amount} = useSelector((store)=>store.cart)
return(
<nav className='nav-center'>
<h3>redux toolkit</h3>
<div className="nav-container">
<CartIcon />
<div className="amount-container">
<p className="total-amount">{amount}</p>
</div>
</div>
</nav>
)
}
export default Navbar;
Lastly, create a file named CartContainer in the components directory and add the following code to the CartContainer file:
import React from 'react'
import CartItem from './CartItem'
import { useSelector, useDispatch } from 'react-redux'
import { openModal } from '../features/modal/modalSlice'
const CartContainer = () => {
const dispatch = useDispatch();
const {cartItems, total, amount} = useSelector((store)=>store.cart)
return (
<>
{amount < 1 && (
<section className="cart">
<header>
<h2>
your bag
</h2>
<h4 className="empty-cart">is currently empty</h4>
</header>
</section>
) }
{amount && (
<section className="cart">
<header>
<h2>your bag</h2>
</header>
<div>
{cartItems.map((item)=> (
<CartItem key={item.id} {...item} />
))}
</div>
<footer>
<hr />
<div className="cart-total">
<h4>
total <span>${total.toFixed(2)}</span>
</h4>
</div>
<button className="btn clear-btn"
onClick={()=>dispatch(openModal())}>clear cart</button>
</footer>
</section>
)}
</>
)
}
export default CartContainer
In the code above, we see that we map through the cartItems and pass each item to the CartItem component. So we need to create the CartItem component. And to do this, create a file called CartItem.jsx in the components directory and add the following code to the CartItem.jsx file:
import React from 'react'
import { ChevronDown, ChevronUp } from '../icons'
import { useDispatch } from 'react-redux'
import { removeItem, increase, decrease } from '../features/cart/cartSlice'
const CartItem = ({id, img, title, price, amount}) => {
const dispatch = useDispatch()
return (
<article className="cart-item">
<img src={img} alt={title} />
<div>
<h4>{title}</h4>
<h4 className="item-price">${price}</h4>
<button className="remove-btn"
onClick={()=> dispatch(removeItem(id))}>remove</button>
</div>
<div>
<button className="amount-btn"
onClick={()=> dispatch(increase(id))}>
<ChevronUp />
</button>
<p className="amount">{amount}</p>
<button className="amount-btn"
onClick={()=> {
if(amount === 1) {
dispatch(removeItem(id))
return;
}
dispatch(decrease(id))}}>
<ChevronDown />
</button>
</div>
</article>
)
}
export default CartItem
Now, when you view the application, you get:

Conclusion
Redux Toolkit has completely transformed how developers use Redux.
What was once seen as too complicated is now simple, elegant, and enjoyable.
The above application is a simple application that shows how Redux Toolkit is set up and the overall structure of an application. However, I do hope that by following this application, you will get an understanding of Redux Toolkit and how it is used with React.
If you’re still managing state with plain Redux, now is the time to switch. You’ll write cleaner code, reduce bugs, and enjoy a better development experience.