Simplifying State Management with Redux in MERN Applications
In this project, we’ve developed a todo web application utilizing the MERN stack (MongoDB, Express.js, React.js, Node.js) with Redux for state management. In this project, a todo can be created, viewed, and also saved in the database.
Output Preview: Let us have a look at how the final output will look like.
Prerequisites:
Approach to Implement State Management in Todo App:
- Brainstorming on features : Before moving to coding part, brainstorm on its features is important. Its feature may be creation, save to local storage or database , display it etc.
- Creating backend : Set up a new Node.js project and install required package like express, mongoose etc. Connect backend to database and then make APIs.
- Creating Frontend : Initialize a react app. Make components like todo , addtodo and these files reside inside src directory.
- Manage state with redux : Make store, actions and reducers for managing and holding state to avoid props drilling.
- Connect frontend to backend : Fetching and storing data from frontend to backend with the help of APIs.
Steps to Create Backend:
Step 1 : Create a root directory named backend and navigate to it.
mkdir backend
cd backend
Steps 2 : Now initialize node.js project here.
npm init -y
Step 3 : Install required module and their dependencies.
npm i express mongoose cors dotenv
package.json:
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"mongoose": "^8.2.1"
}
Project Structure(Backend):
Example: Below is the code example for backend:
// server.js
const mongoose = require('mongoose')
const databaseConnection = () => {
mongoose.connect(process.env.MONGOOSE_URI)
console.log('Database connected successfully.')
return true;
}
// defining schema
const todosSchema = new mongoose.Schema({
id: String,
message: String,
date: String
})
// defining todo model
const Todo = mongoose.model('Todo', todosSchema)
module.exports = {
Todo,
databaseConnection
}
// app.js
const express = require('express')
const cors = require('cors')
const dotenv = require('dotenv')
const { Todo, databaseConnection } = require('./server')
const app = express()
dotenv.config()
// calling database
databaseConnection();
// calling middlewares
app.use(express.json())
app.use(cors())
const PORT = process.env.PORT || 8000
// fetching
app.get('/api/get', async (req, res) => {
try {
const todos = await Todo.find();
return res.json({ success: true, todos })
} catch (error) {
return res.json({ success: false, data: {}, error })
}
})
// saving todos
app.post('/api/save', async (req, res) => {
try {
const todos = req.body
let data = new Todo(todos[1])
await data.save();
return res.json({ success: true, data })
} catch (error) {
return res.json({ success: false, data: {}, error })
}
})
// listing server
app.listen(PORT, () => {
console.log(`Server is running on PORT : ${PORT}`)
})
Steps to Create Frontend:
Step 1 : Create a root directory named frontend and navigate to it.
npm frontend
cd frontend
Step 2 : Create a react app using this command.
npx create-react-app
Steps 3 : Install required package for this project.
npm i react-redux @reduxjs/toolkit cors
package.js:
"dependencies": {
"@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.7",
"react": "^18.2.0",
"react-redux": "^9.1.0",
"redux": "^5.0.1"
},
Project Structure:Frontend Project Structure
Example: Below is the code example frontend:
/* style.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app_wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
#input_box {
width: 400px;
border-radius: 10px 0px 0px 10px;
padding: 10px 20px;
border: 2px solid black;
border-right: none;
outline: none;
text-transform: capitalize;
}
#add_btn {
padding: 10px 20px;
border-radius: 0px 10px 10px 0px;
background-color: #44a075;
cursor: pointer;
color: white;
}
#todo_banner {
background-color: #44a075;
text-align: center;
padding: 10px 0px;
color: white;
font-weight: bolder;
width: 100vw;
}
#add_todo_wrapper {
margin: 20px 0px;
}
/* todos component css */
.todo {
display: flex;
border: 1px solid black;
padding: 5px 20px;
border-radius: 10px;
align-items: center;
gap: 20px;
width: 400px;
margin-bottom: 5px;
text-transform: capitalize;
justify-content: space-around;
}
.todo img {
width: 30px;
height: 30px;
border-radius: 50%;
}
#save_btn,
#get_todos_btn {
padding: 10px 20px;
border-radius: 10px;
background-color: #44a075;
cursor: pointer;
color: white;
border: 2px solid black;
margin: 5px;
}
// store.js
// creating slice
import { createSlice, configureStore } from "@reduxjs/toolkit";
// initial state of the todo slice
const initialState = {
todos: [
{
id: '',
message: '',
date: ''
}
]
}
const todoSlice = createSlice({
name: 'Todo Slice',
initialState,
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload)
},
}
})
export const { addTodo } = todoSlice.actions
// creating store
const store = configureStore({
reducer: todoSlice.reducer
})
export default store;
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import {
Todos, AddTodo,
GetDatabaseTodos, SaveToDatabase
} from './TodoComp'
import { Provider } from 'react-redux'
import './style.css'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<div id='app_wrapper' >
<h2 id='todo_banner'>Your's Todo</h2>
<AddTodo />
<Todos />
<GetDatabaseTodos />
<SaveToDatabase />
</div>
</Provider>
);
// TodoComp.js
import React, { useState } from "react";
import axios from "axios";
import { useSelector, useDispatch } from "react-redux";
import { addTodo } from './store'
export function Todos() {
const todos = useSelector((state) => state.todos)
console.log(todos);
return (
<div id="todos_wrapper">
{
todos && todos.map((todo) => {
if (todo.message)
return (
<div className='todo' key={todo.id} >
<span>{todo.message}</span>
<span> <img src=
"https://media.w3wiki.net/gfg-gg-logo.svg"
alt="gfg_logo" /> </span>
<span>{todo.date}</span>
</div>
)
})
}
</div >
)
}
export function GetDatabaseTodos() {
const dispatch = useDispatch()
const getTodosHandle = async () => {
try {
let todos =
await axios.get(
'https://gray-teacher-awrcf.pwskills.app:8000/api/get')
todos = todos.data.todos
console.log(todos);
todos.map((todo) => {
return dispatch(addTodo(
{
id: todo.id, message: todo.message,
date: todo.date
}))
})
} catch (error) {
return alert('Please try later.')
}
}
return (
<button id='get_todos_btn'
onClick={getTodosHandle}>
Get Database's Todos
</button>
)
}
export function SaveToDatabase() {
const todos = useSelector((state) => state.todos)
const saveHandle = async () => {
try {
await axios.post(
'https://gray-teacher-awrcf.pwskills.app:8000/api/save', todos)
return alert('Todos save successfully!')
} catch (error) {
return alert('Todos save failed!')
}
}
return (
<button id='save_btn'
onClick={saveHandle}>
Save To DataBase
</button>
)
}
export function AddTodo() {
const dispatch = useDispatch();
const [message, setMessage] = useState('')
const addTodoHandle = () => {
if (message.trim() === '') return
let date = new Date()
date = `${date.getDate()}-${date.getMonth()}-
${date.getFullYear()} ${date.getHours()}:
${date.getMinutes()}`
dispatch(addTodo(
{
id: Math.random().toFixed(5),
message, date
}))
setMessage('')
}
return (
<div id="add_todo_wrapper">
<input type="text" id="input_box"
value={message}
onChange={
(e) => setMessage(e.target.value)
} />
<button id="add_btn" onClick={addTodoHandle} >Add</button>
</div>
)
}
Steps to Run the Application:
To start Backend Server : Go to backed folder and follow this command to start backend server.
node ./app.js
To start Frontend Server : Go to frontend folder and follow this command to start frontend server.
npm start
Output:
- Browser Output
- Data Saved in Database:
Contact Us