Open In App

Simplifying State Management with Redux in MERN Applications

Last Updated : 27 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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.

1711022222458

Final Output

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):

backend-project-structure

Backend Project Structure

Example: Below is the code example for backend:

JavaScript
// 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
}
JavaScript
// 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:
1711023199952

Frontend Project Structure

Example: Below is the code example frontend:

CSS
/* 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;
}
JavaScript
// 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; 
JavaScript
// 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>
);
JavaScript
// 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.geeksforgeeks.org/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
Final-output

Browser Output

  • Data Saved in Database:
Output of data saved in Database

Output of data saved in Database




Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads