How to Build a Todo App with Redux Toolkit, API Integration, and Tailwind CSS
In this tutorial, we’re going to build a simple Todo app using Redux Toolkit and React. We’ll also include API integration, so you get real-time data updates, and style it with Tailwind CSS for an easy, modern design. I’ll explain every piece of code in a way that makes sense for beginners, with analogies and examples to make each concept clear. And remember, this is brought to you by Martin Lubowa X AI. For more of my work, visit my GitHub.
Overview of What We’ll Build
Imagine you have a whiteboard at home where you list things to do. Now, imagine this whiteboard can store every task, even if you turn off the lights and leave the room. That’s what Redux does: it’s like a central command center that remembers everything in your app, so you don’t lose your list when things change or refresh. Redux also allows you to fetch information from online APIs, such as inspiring quotes to keep you motivated. Here’s what you’ll learn:
Setting up Redux to manage state in React.
Creating and managing Todos with Redux Toolkit.
Fetching quotes from an API to display alongside your Todos.
Styling with Tailwind CSS to make the app visually appealing.
Step 1: Setting Up the Project
1.1. Create Your Project Using Vite
Vite is like a turbo engine for setting up a React project quickly. Let’s create our project and install the necessary dependencies.
npm create vite@latest redux-todo-app --template react
cd redux-todo-app
npm install
1.2. Install Redux Toolkit and React-Redux
npm install @reduxjs/toolkit react-redux
@reduxjs/toolkit simplifies using Redux by providing tools to set up the command center for our app.
react-redux is a bridge between our React app and the Redux command center, allowing our app to get updates or send changes to the command center.
Step 2: Organize Your Files
To keep things neat, we’ll create a few folders:
app/
for the Redux store.features/todos/
for the Todo feature files.
Step 3: Setting Up the Store (Our Command Center)
A Redux store is like a big bulletin board where all the app’s information (or state) lives.
3.1. Create the Store in src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from '../features/todos/todoSlice';
export const store = configureStore({
reducer: {
todos: todoReducer,
},
});
configureStore: This is a helper from Redux Toolkit that sets up the store.
reducer: Reducers are like teachers that handle specific tasks—this one will manage Todos. We'll define this reducer next.
Think of todoReducer
as the dedicated manager for everything related to Todos. It tells the store what to do when new tasks are added or removed.
3.2. Connect the Store to the App in main.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './app/store';
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
);
- Provider: Just as Wi-Fi connects devices to the internet,
Provider
connects our app to the Redux store, enabling every component to access it.
Step 4: Create the Todo Slice (Action Bundle) with Redux Toolkit
A slice in Redux is like a toolkit with all actions and data related to a single part of our app—in this case, the Todo list. Here, we’ll define actions like adding, toggling (marking as done), and removing Todos.
4.1. Define the Slice in src/features/todos/todoSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
const initialState = {
items: [],
status: 'idle',
quote: '',
};
export const fetchQuote = createAsyncThunk('todos/fetchQuote', async () => {
const response = await fetch('https://api.quotable.io/random');
const data = await response.json();
return data.content;
});
const todoSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo: (state, action) => {
state.items.push({ id: Date.now(), text: action.payload, completed: false });
},
toggleTodo: (state, action) => {
const todo = state.items.find((todo) => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
removeTodo: (state, action) => {
state.items = state.items.filter((todo) => todo.id !== action.payload);
},
},
extraReducers: (builder) => {
builder
.addCase(fetchQuote.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchQuote.fulfilled, (state, action) => {
state.status = 'succeeded';
state.quote = action.payload;
})
.addCase(fetchQuote.rejected, (state) => {
state.status = 'failed';
state.quote = 'Failed to fetch quote';
});
},
});
export const { addTodo, toggleTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer;
Reducers: Each one is a "command" our app can execute—like adding, removing, or toggling a Todo.
createAsyncThunk: Imagine you want to fetch a quote from the internet.
createAsyncThunk
is the go-between that handles these “outside calls.”extraReducers: This is where we connect our
fetchQuote
action to Redux. EachaddCase
manages what happens at different stages: pending (waiting), fulfilled (successful), or rejected (failed).
Think of extraReducers
as a set of specific instructions for handling API calls. When we start fetching a quote, it sets the status to “loading”; once it’s done, it updates the quote in the state.
Step 5: Build the AddTodo Component
This component allows users to add new items to the Todo list.
5.1. Create src/components/AddTodo.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo, fetchQuote } from '../features/todos/todoSlice';
const AddTodo = () => {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
dispatch(addTodo(text));
dispatch(fetchQuote());
setText('');
}
};
return (
<form onSubmit={handleSubmit} className="flex gap-4">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new todo"
className="border border-gray-300 p-2 rounded w-full"
/>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
Add Todo
</button>
</form>
);
};
export default AddTodo;
useState: Keeps track of user input, storing what they type until they hit the “Add Todo” button.
dispatch: This function sends our commands (like adding a Todo) to Redux.
fetchQuote: This command will fetch a new quote each time a Todo is added.
Step 6: Display Todos in TodoList
6.1. Create src/features/todos/TodoList.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { toggleTodo, removeTodo } from './todoSlice';
const TodoList = () => {
const todos = useSelector((state) => state.todos.items);
const quote = useSelector((state) => state.todos.quote);
const dispatch = useDispatch();
return (
<div className="mt-6">
<h2 className="text-2xl font-bold mb-4">My Todo List</h2>
<p className="italic text-gray-500 mb-4">"{quote}"</p>
<ul>
{todos.map((todo) => (
<li key={todo.id} className="flex items-center gap-2 mb-2">
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch(toggleTodo(todo.id))}
className="h-4 w-4"
/>
<span
className={`flex-1 ${todo.completed ? 'line-through text-gray-400' : ''}`}
>
{todo.text}
</span>
<button
onClick={() => dispatch(removeTodo(todo.id))}
className="bg-red-500 text-white px-2 py-1 rounded"
>
Delete
</button>
</li>
))}
</ul>
</div>
);
};
export default TodoList;
useSelector: It allows us to read values from the Redux store. Here, we pull in our Todos and the random quote.
toggleTodo and removeTodo: These are dispatched when the user checks/unchecks or deletes a Todo.
Step 7: Combine Everything in App.js
7.1. Update src/App.js
import React from 'react';
import AddTodo from './components/AddTodo';
import TodoList from './features/todos/TodoList';
const App = () => {
return (
<div className="max-w-xl mx-auto p-4">
<h1 className="text-3xl font-bold text-center">Todo App</h1>
<AddTodo />
<TodoList />
</div>
);
};
export default App;
This file brings everything together in a single place. The App
component is like the head chef that coordinates the kitchen, making sure all the different dishes (components) come together nicely.
Step 8: Style with Tailwind CSS
8.1. Set Up Tailwind CSS
If you haven’t set up Tailwind CSS yet, follow these steps:
- Install Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
- Configure your
tailwind.config.js
file:
module.exports = {
content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
- Add the Tailwind directives to your
src/index.css
:
@tailwind base;
@tailwind components;
@tailwind utilities;
This makes your app look sleek and modern!
Step 9: Run Your App
Finally, let’s run our Todo app! In your terminal, run:
npm run dev
Visit http://localhost:5173/
in your browser, and you’ll see your brand-new Todo app in action! You can add Todos, toggle their completion, and get a fresh quote with each new task.
Conclusion
Congratulations! You’ve just built a Todo app using Redux Toolkit, integrated it with an API to fetch motivational quotes, and styled it with Tailwind CSS. You’ve learned about the importance of state management, how to fetch data from APIs, and how to create a visually appealing interface.
Feel free to extend this app by adding features like editing Todos, filtering them, or even saving them to local storage for persistence. For more of my work, check out my GitHub.
Happy coding!
Subscribe to my newsletter
Read articles from Martin Lubowa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Martin Lubowa
Martin Lubowa
Martin Lubowa is a software engineer passionate about using technology to merge entrepreneurship with education/healthcare sectors in Africa to build resilient and prosperous enterprises. He has been the co-founder and managing director of the Africa Students Support Network (AFRISSUN), a community-based non-organization in Uganda. He has led several charity drives to mobilize food/educational resources for underserved communities.