Redux is a state management tool for JavaScript applications.
It provides a store for our applications and methods to get the state, listen to changes in your state, and update it.
In this article, you will learn how to use Redux in your React application by creating a simple todo application.
Repository
Check out the complete source code here
Prerequisites
The todo Application will be built with React and Redux.
Basic Javascript Knowledge
Node installed on your device
Basic understanding of React
Three Main Parts of Redux
Redux contains three main parts:
Store: a state container that holds all the application state and provides methods to interact with the state.
Actions: a plain object describing the change that occurred in your application. It must contain a type field indicating the action being performed.
Reducers: reducer is a function that takes the current state and the action which occurred and returns a new state.
Setting up Redux in a React application
Step 1: Install Redux and react-redux
npm install redux react-redux
Step 2: Create a Redux folder in your src directory
The src/redux
directory will contain your reducers, actions, and store.
Step 3: Create a todo folder in your redux folder
The src/redux/todo
will contain your todoActions.js
and todoReducer.js
files.
Step 4: Create your actions
For each action object, add the type of the action and the payload, if necessary.
src/redux/todo/todoActions.js
export const addTask = (data) => ({
type: "ADD_TASK",
payload: data,
});
export const fetchTasks = (data) => ({
type: "FETCH_TASK",
payload: data,
});
export const deleteTasks = (task) => ({
type: "DELETE_TASK",
payload: task,
});
export const updateTasks = (task) => ({
type: "UPDATE_TASK",
payload: task,
});
step 5: Create your reducers
Define the initial state of your todo application and create a Reducer function that takes in your initial state and action as parameters. State in react is immutable, meaning it should not change directly. So for every condition, a copy of the state is first made using the spread operator ...state
.
src/redux/todo/todoReducers.js
const INITIAL_STATE = {
todos: [],
};
const todoReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case "ADD_TASK":
return {
...state,
todos: [...state.todos, action.payload],
};
case "FETCH_TASK":
return {
...state,
todos: action.payload,
};
case "UPDATE_TASK":
return {
...state,
todos: state.todos.map((todo) => {
if (todo.id === action.payload) {
todo.title = action.payload.title;
}
}),
};
case "DELETE_TASK":
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload),
};
default:
return state;
}
};
export default todoReducer;
step 6: Create your root-reducer
As the application grows more complex, it can contain multiple reducers which manage independent parts of your state; with the help of the combineReducers helper function provided by the Redux library, you can import all the reducers in the application, and export a single root reducer file.
src/redux/root-reducer.js
import { combineReducers } from "redux";
import todoReducer from "./todo/todoReducer";
const rootReducer = combineReducers({
todo: todoReducer,
});
export default rootReducer;
Step 7: Create the store
Create a store.js file in the src/redux
folder. Add a middleware called redux-logger to help log your state changes in the console.
src/redux/store.js
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import rootReducer from "./root-reducer";
const middlewares = [];
if (process.env.NODE_ENV === "development") {
middlewares.push(logger);
}
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
step 8: Connect your Redux store to your application
Connecting your Redux store to your application requires wrapping your main app component in your src/index.js
file with a Provider component, and passing your store into the provider component like a prop.
src/index.js
import { Provider } from "react-redux";
import store from "./Redux/store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
Create a demo rest JSON web service using json-server and a db.json
file with sample data in the src
folder.
Redux has libraries like Redux-thunk for performing asynchronous tasks and data fetching, but handling asynchronous tasks in this application will be done in components.
Install json webserver
npm install -g json-sever
Using Redux from the components
Now that you have set up the redux store, you can consume state and dispatch actions using useSelector and useDispatch hooks.
This form is the input form to create a task
AddForm.jsx
<form onSubmit={handleSubmit}>
<input
name="task"
placeholder="Add a task"
type="task"
value={task}
onChange={handleChange}
/>
<button>Add task</button>
</form>
And in the handleSubmit handler function, make a request to the demo web service to add a task and dispatch our addTask
action.
handleSubmit
const handleSubmit = async (e) => {
e.preventDefault();
const {data} = await axios.post("http://localhost:4000/tasks", {
title: task,
});
dispatch(addTask(data));
};
Fetch All Tasks
Fetch all tasks in the app.js component and pass them to the AddForm.jsx
component as a prop for it to be rendered.
In the useEffect hook, send a get request to your endpoint to get all the tasks and dispatch a fetchTasks action with the data obtained from the response as the payload.
useEffect(() => {
async function fetchData() {
const { data } = await axios.get("http://localhost:4000/tasks");
dispatch(fetchTasks(data));
}
fetchData();
}, []);
Deleting a Task
To delete a task, create a handleDelete
function and in it, send a delete request to your endpoint.
delete task
const handleDelete = async (id) => {
const res = await axios.delete(`http://localhost:4000/tasks/${id}`);
dispatch(deleteTasks(id));
};
Updating a Task
To update a task, create an input form in which you add your new update and also a handleUpdate
function that takes care of sending a patch request to the endpoint.
UpdateForm.jsx
<form onSubmit={handleSubmit}>
<input
name="task"
placeholder="Add a task"
type="task"
value={task}
onChange={handleChange}
/>
<button onClick={()=>handleUpdate(params.id)}>Update task</button>
</form>
handleUpdate
const handleUpdate = async (id) => {
const response = await axios.patch(`http://localhost:4000/tasks/${id}`, {
title: task,
});
dispatch(updateTasks(task))
};
After completing the application, your demo application will look like this:
Conclusion
When working with small-scale applications, using Redux for state management adds an extra layer of complexity that is not necessary since the state does not need to be passed down to many child components.
For large-scale applications, managing state with Redux provides a single source of truth, making it easier to understand data flow throughout the application and providing state value to different components in our application.