dev Await
March 2025
What is Redux? How Can I Use Redux & Redux Toolkit in React.js?
Managing state in React.js can get tricky as your app grows. That’s where Redux comes in—a library that keeps your app’s state in one place for predictability. Pair it with Redux Toolkit, and you get a simpler, modern way to handle state. In this guide, we’ll explore Redux, Redux Toolkit, and advanced features like createAsyncThunk for API calls and RTK Query, all with an easy-to-follow example.
What is Redux?
Redux is a JavaScript library for centralized state management. It stores your app’s state in a single "store," making it easier to track and update.
Core Ideas
Single Store: All state lives in one place.
Actions: Tell Redux what to change (e.g., "add item").
Reducers: Update the state based on actions.
Why Use It?
Predictable state changes.
Easier debugging with tools like Redux DevTools.
Scales well for big apps.
What is Redux Toolkit?
Redux Toolkit is the official way to use Redux with less code. It simplifies setup and adds handy tools like createSlice and configureStore.
Setting Up a Simple To-Do App
Let’s build a to-do app with Redux Toolkit to see it in action.
Step 1: Setup
Create a React app and install dependencies:
npx create-react-app todo-app
cd todo-app
npm install @reduxjs/toolkit react-redux
Step 2: Create the Store
In src/store.js:
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './features/todoSlice';
const store = configureStore({
reducer: {
todos: todoReducer,
},
});
export default store;
Step 3: Create a Todo Slice
In src/features/todoSlice.js:
import { createSlice } from '@reduxjs/toolkit';
const todoSlice = createSlice({
name: 'todos',
initialState: {
items: [],
},
reducers: {
addTodo: (state, action) => {
state.items.push(action.payload);
},
removeTodo: (state, action) => {
state.items = state.items.filter((_, index) => index !== action.payload);
},
},
});
export const { addTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer;
Step 4: Connect to React
In src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Step 5: Build the Todo Component
In src/components/Todo.js:
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, removeTodo } from '../features/todoSlice';
const Todo = () => {
const [task, setTask] = useState('');
const todos = useSelector((state) => state.todos.items);
const dispatch = useDispatch();
const handleAdd = () => {
if (task) {
dispatch(addTodo(task));
setTask('');
}
};
return (
<div>
<h2>My To-Do List</h2>
<input value={task} onChange={(e) => setTask(e.target.value)} placeholder="New task" />
<button onClick={handleAdd}>Add</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo} <button onClick={() => dispatch(removeTodo(index))}>X</button>
</li>
))}
</ul>
</div>
);
};
export default Todo;
Step 6: Use It
In src/App.js:
import Todo from './components/Todo';
function App() {
return <Todo />;
}
export default App;
Run npm start—you’ve got a working to-do list!
Advanced Redux Toolkit Features
Now, let’s level up with two powerful features: createAsyncThunk for API calls and RTK Query for data fetching.
1. createAsyncThunk: Fetching Data from an API
createAsyncThunk simplifies async operations like API calls. Let’s fetch a list of tasks from a fake API.
Update src/features/todoSlice.js:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios'; // Install with `npm install axios`
// Async thunk to fetch todos
export const fetchTodos = createAsyncThunk('todos/fetchTodos', async () => {
const response = await axios.get('https://jsonplaceholder.typicode.com/todos?_limit=5');
return response.data.map((todo) => todo.title);
});
const todoSlice = createSlice({
name: 'todos',
initialState: {
items: [],
loading: false,
},
reducers: {
addTodo: (state, action) => {
state.items.push(action.payload);
},
removeTodo: (state, action) => {
state.items = state.items.filter((_, index) => index !== action.payload);
},
},
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.loading = true;
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.items = action.payload;
state.loading = false;
})
.addCase(fetchTodos.rejected, (state) => {
state.loading = false;
});
},
});
export const { addTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer;
Update src/components/Todo.js:
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, removeTodo, fetchTodos } from '../features/todoSlice';
const Todo = () => {
const [task, setTask] = useState('');
const { items, loading } = useSelector((state) => state.todos);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchTodos()); // Fetch todos on mount
}, [dispatch]);
const handleAdd = () => {
if (task) {
dispatch(addTodo(task));
setTask('');
}
};
return (
<div>
<h2>My To-Do List</h2>
{loading ? <p>Loading...</p> : null}
<input value={task} onChange={(e) => setTask(e.target.value)} placeholder="New task" />
<button onClick={handleAdd}>Add</button>
<ul>
{items.map((todo, index) => (
<li key={index}>
{todo} <button onClick={() => dispatch(removeTodo(index))}>X</button>
</li>
))}
</ul>
</div>
);
};
export default Todo;
Now, your app fetches todos from an API when it loads!
2. RTK Query: Simplified Data Fetching
RTK Query is a built-in tool in Redux Toolkit for fetching and caching data. It’s even easier than createAsyncThunk for API-heavy apps.
Update src/features/todoSlice.js (replace the old code):
import { createSlice } from '@reduxjs/toolkit';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
// Define the API
export const todoApi = createApi({
reducerPath: 'todoApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com/' }),
endpoints: (builder) => ({
getTodos: builder.query({
query: () => 'todos?_limit=5',
transformResponse: (response) => response.map((todo) => todo.title),
}),
}),
});
export const { useGetTodosQuery } = todoApi;
// Todo slice for local state
const todoSlice = createSlice({
name: 'todos',
initialState: {
items: [],
},
reducers: {
addTodo: (state, action) => {
state.items.push(action.payload);
},
removeTodo: (state, action) => {
state.items = state.items.filter((_, index) => index !== action.payload);
},
},
});
export const { addTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer;
Update src/store.js:
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './features/todoSlice';
import { todoApi } from './features/todoSlice';
const store = configureStore({
reducer: {
todos: todoReducer,
[todoApi.reducerPath]: todoApi.reducer, // Add RTK Query reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(todoApi.middleware), // Add RTK Query middleware
});
export default store;
Update src/components/Todo.js:
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, removeTodo } from '../features/todoSlice';
import { useGetTodosQuery } from '../features/todoSlice';
const Todo = () => {
const [task, setTask] = useState('');
const { data: apiTodos = [], isLoading } = useGetTodosQuery(); // Fetch todos with RTK Query
const localTodos = useSelector((state) => state.todos.items);
const dispatch = useDispatch();
const handleAdd = () => {
if (task) {
dispatch(addTodo(task));
setTask('');
}
};
const allTodos = [...apiTodos, ...localTodos]; // Combine API and local todos
return (
<div>
<h2>My To-Do List</h2>
{isLoading ? <p>Loading...</p> : null}
<input value={task} onChange={(e) => setTask(e.target.value)} placeholder="New task" />
<button onClick={handleAdd}>Add</button>
<ul>
{allTodos.map((todo, index) => (
<li key={index}>
{todo} {index >= apiTodos.length ? <button onClick={() => dispatch(removeTodo(index - apiTodos.length))}>X</button> : null}
</li>
))}
</ul>
</div>
);
};
export default Todo;
With RTK Query, fetching and caching are automatic—no manual useEffect needed!
When to Use These Features?
createAsyncThunk: Great for one-off API calls with custom logic.
RTK Query: Perfect for apps with lots of data fetching, caching, and automatic refetching.
Conclusion
Redux and Redux Toolkit make state management in React.js simple and scalable. With createAsyncThunk, you can handle API calls easily, and RTK Query takes it further with built-in data fetching. Try these in your next project to see the difference!
Keep learning, and check out devAwait for more tech tips!