DEV Community

Cover image for Understanding the prepare Callback In Redux Toolkit
Chinedu Oputa
Chinedu Oputa

Posted on

Understanding the prepare Callback In Redux Toolkit

Introduction

Redux Toolkit (RTK) has simplified the way developers write Redux logic by providing utilities that make state management more efficient, less verbose, and easier to maintain. And one of its powerful features is the prepare callback function.

If you’ve ever wanted to customize the payload or preprocess data before it reaches your reducers, the prepare callback is the right tool for the job. In this article, we’ll break down what the prepare callback is, how it works, and how you can use it effectively in your Redux applications — with clear, practical examples.

The prerequisite to gain the most from this article is given below, in the next section.

Prerequisite

NodeJS should be installed on your system
Knowledge of React
Knowledge of JavaScript
Knowledge of Redux Toolkit

Getting Started

In this section, we will learn what the prepare callback is and why we should use the prepare callback.

What Is the prepare Callback?

In Redux Toolkit, when you create a reducer using createSlice, each reducer automatically generates an action creator. By default, the action creator takes one argument — the payload — and passes it directly to the reducer.
However, sometimes you need more control over what the payload looks like before it’s passed to the reducer. For example:
You might want to add extra metadata like a timestamp or a unique ID.

You might want to merge multiple arguments into one structured payload.

You might need to transform or validate input before storing it.

That’s where the prepare callback comes in.
The prepare callback lets you preprocess action payloads before they reach your reducer. Essentially, it defines how the action’s payload (and optionally meta and error fields) should be structured.

Why Use the prepare Callback?

The prepare function is useful whenever your action payload needs more structure or logic than just passing a single argument.
Here are a few reasons why you might want to use it:

  1. Add Metadata Automatically You can enrich actions with fields like timestamps, unique IDs, or user context.
  2. Normalize Data If you receive nested or complex data, prepare can help flatten or reformat it before the reducer processes it.
  3. Handle Multiple Arguments Normally, Redux action creators only accept one argument for the payload. With prepare, you can accept multiple arguments and decide how to package them.
  4. Consistency By centralizing how payloads are structured, your reducers become cleaner and more predictable. To learn about the prepare callback, let’s build a blog with Redux Toolkit.

Building a Blog with React and Redux Toolkit

To build our blog, we first need to bootstrap a React application using Vite. Vite is a super-fast build tool that replaces Create React App (CRA) for modern React development. To use Vite, in your terminal, run the code below:

npm create vite@latest blog-app
Enter fullscreen mode Exit fullscreen mode

You’ll be prompted with something like this:

? Select a framework: › - Use arrow-keys. Return to submit.
  Vanilla
❯ React
  Vue
  Svelte
  Others
Enter fullscreen mode Exit fullscreen mode

Choose React.
Now go into the app folder by running:

cd blog-app
Enter fullscreen mode Exit fullscreen mode

Then run the app with:

npm run dev
Enter fullscreen mode Exit fullscreen mode

After running the code above, to view the app, visit http://localhost:5173/, and you will get:

React and Vite logos

Next, install Redux Toolkit and the react-redux package by running:

npm install @reduxjs/toolkit react-redux
Enter fullscreen mode Exit fullscreen mode

Now we can set up Redux Toolkit inside the app. To do this, we configure the Redux store by following the steps in the next section.

Configure the Store

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";

export const store = configureStore({
  reducer: { },
});
Enter fullscreen mode Exit fullscreen mode

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 { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import { store } from "./app/store.js";
import { Provider } from "react-redux";

createRoot(document.getElementById("root")).render(
 <StrictMode>
   <Provider store={store}>
     <App />
   </Provider>
 </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

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 Slice

To create the postSlice, create a folder called features inside the src directory, and inside the features directory, create a file named postSlice.js and add the code below to the postSlice.js:

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

const initialState = {
 posts: [
   { id: 1, title: "Learning Redux Toolit", content: "Redux is amazing!" },
   { id: 2, title: "Mastering React", content: "React is awesome!" },
 ],
};

const postSlice = createSlice({
 name: "posts",
 initialState,
 reducers: {
   postAdded(state, action) {
     state.posts.push(action.payload);
   },
 },
});

export const { postAdded } = postSlice.actions;
export default postSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

In the code above, RTK automatically creates actions with the same name as the reducers provided, and these actions are exported.
Also, RTK uses Immer.js internally, allowing you to write “mutating” logic safely, as seen in the reducers in the code above.
Next, we need to add the created reducer to the store. To do this, update the store.js file as seen in the code below:

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

export const store = configureStore({
 reducer: {
   posts: postReducer,
 },
});
Enter fullscreen mode Exit fullscreen mode

Create The Add Post Form

Before we display the post list, let's create the form to add new posts. To do this, create a file named AddPostForm .jsx inside the posts directory that is in the features directory. Now, add the following code to the AddPostForm.jsx:

import { useState } from "react";
import { useDispatch } from "react-redux";

import { postAdded } from "./postSlice";
import { nanoid } from "@reduxjs/toolkit";

const AddPostForm = () => {
 const dispatch = useDispatch();

 const [title, setTitle] = useState("");
 const [content, setContent] = useState("");

 const onTitleChanged = (e) => setTitle(e.target.value);
 const onContentChanged = (e) => setContent(e.target.value);

 const onSavePostClicked = () => {
   if (title && content) {
     dispatch(
       postAdded({
         id: nanoid(),
         title,
         content,
       })
     );
     setTitle("");
     setContent("");
   }
 };

 return (
   <section>
     <h2>Add a New Post</h2>
     <form>
       <label htmlFor="postTitle">Post Title:</label>
       <input
         type="text"
         id="postTitle"
         name="postTitle"
         value={title}
         onChange={onTitleChanged}
       />
       <label htmlFor="postContent">Content:</label>
       <textarea
         id="postContent"
         name="postContent"
         value={content}
         onChange={onContentChanged}
       />
       <button type="button" onClick={onSavePostClicked}>
         Save Post
       </button>
     </form>
   </section>
 );
};
export default AddPostForm;
Enter fullscreen mode Exit fullscreen mode

In the code above, the input and textarea are controlled components, while the onSavePostClicked function is called when the form is submitted.
The onSavePostClicked function checks if there is a title and content value before dispatching an action by invoking the postAdded action creator. The postAdded action creator takes an object that contains the post ID — added using the nanoid package that comes with Redux Toolkit, the title, and the content.

Use Store In Components

Todo this, inside the posts directory in the features directory, create a Postlist.jsx file and add the following code:

import { useSelector } from "react-redux";

const PostList = () => {
 const posts = useSelector((state) => state.posts.posts);

 return (
   <section>
     <h2>Posts</h2>
     {posts.map((post) => (
       <article key={post.id}>
         <h3>{post.title}</h3>
         <p>{post.content.substring(0, 100)}</p>
       </article>
     ))}
   </section>
 );
};
export default PostList;
Enter fullscreen mode Exit fullscreen mode

Now update the App.jsx file by replacing the code with the following:

import PostList from "./features/posts/PostList";
import AddPostForm from "./features/posts/AddPostForm";

function App() {
 return (
   <main className="App">
     <AddPostForm />
     <PostList />
   </main>
 );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Finally, replace the code in the index.css file with the following:

* {
 margin: 0;
 padding: 0;
 box-sizing: border-box;
}

html {
 font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
 background: #333;
 color: whitesmoke;
}

body {
 min-height: 100vh;
 font-size: 1.5rem;
 padding: 0 10% 10%;
}

input,
textarea,
button,
select {
 font: inherit;
 margin-bottom: 1em;
}

main {
 max-width: 500px;
 margin: auto;
}

section {
 margin-top: 1em;
}

article {
 margin: 0.5em 0.5em 0.5em 0;
 border: 1px solid whitesmoke;
 border-radius: 10px;
 padding: 1em;
}

h1 {
 font-size: 3.5rem;
}

p {
 font-family: Arial, Helvetica, sans-serif;
 line-height: 1.4;
 font-size: 1.2rem;
 margin: 0.5em 0;
}

form {
 display: flex;
 flex-direction: column;
}

.postCredit {
 font-size: 1rem;
}
Enter fullscreen mode Exit fullscreen mode

Now, when we view our app, we get:

Blog app image

Refactoring to use the Prepare Callback

Now, when you test the app, everything works fine. You can add new posts and view them. However, we can improve our app by using the prepare callback. Currently, in the AddPostForm.jsx component, the form is working properly, but it needs to know the details about the state in order to send it properly, as seen in the code below:


const onSavePostClicked = () => {
   if (title && content) {
     dispatch(
       postAdded({
         id: nanoid(),
         title,
         content,
       })
     );
     setTitle("");
     setContent("");
   }
 };

Enter fullscreen mode Exit fullscreen mode

In the code above, we have to send a properly formatted object from the postAdded action creator, but we can abstract this since we do not want to duplicate this kind of logic in every component that posts to our global state. So a better way to do this is to handle this back in the postSlice using the prepare callback.

The prepare callback can generate unique IDs, format the data, and return the object with the payload. And this will simplify our component.
To do this, replace the code in the postSlice with the following:

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

const initialState = {
 posts: [
   { id: 1, title: "Learning Redux Toolit", content: "Redux is amazing!" },
   { id: 2, title: "Mastering React", content: "React is awesome!" },
 ],
};

const postSlice = createSlice({
 name: "posts",
 initialState,
 reducers: {
   postAdded: {
     reducer(state, action) {
       state.posts.push(action.payload);
     },
     prepare(title, content) {
       return {
         payload: {
           id: nanoid(),
           title,
           content,
         },
       };
     },
   },
 },
});

export const { postAdded } = postSlice.actions;
export default postSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

The benefit of this change is that our component — in this case, the AddPostForm.jsx component does not need to know the structure of the state since that is now handled inside of the postSlice by the prepare callback, as seen in the code above.
Now, update the onSavePostClicked function in the AddPostForm.jsx component as seen below:

const onSavePostClicked = () => {
   if (title && content) {
     dispatch(postAdded(title, content));
     setTitle("");
     setContent("");
   }
 };
Enter fullscreen mode Exit fullscreen mode

From the code above, we see that we can simply pass in the raw data — title and content to the postAdded action creator, keeping our component simple and clean.
Finally, test the app and everything still works fine.

Conclusion

The prepare callback in Redux Toolkit is a small but powerful feature that helps you write cleaner, more maintainable Redux code. It gives you control over how actions are structured before they hit your reducers, making your state updates more predictable.

And I hope that by going through this tutorial, you have learned something new about Redux Toolkit.

💼 Hire Me

I'm currently available for freelance or full-time frontend or backend development work.

What I do:

  • React, Redux Toolkit, NodeJS, ExpressJS, and TailwindCSS
  • Building scalable web applications
  • Creating responsive, user-friendly UIs

📧 Email: Leave a messsage

🌐 Portfolio: My portfolio
💬 LinkedIn: linkedin.com/in/yourprofile

If you like my work, let’s collaborate!

Top comments (0)