Create a MERN CRUD Application Part 2

5 months ago admin Nodejs

In the second part of this tutorial, we will create update, and delete tasks and finally, we will fetch and display all the tasks in the home component.


Create the Header component

First, let's run the command inside the main project folder :

npx create-react-app frontend

Next, let's install the packages:

npm install react-router-dom axios

We will use Bootstrap for styling so copy the CSS and JS links from here and put them in the head of the public/index.html file.

Next inside the folder frontend create a new folder and name it 'components' Inside add a new folder and name it 'layouts',  inside create a new component and name it 'Header.vue'. 

                                                        
                                                                                                                        
import React from 'react'
import { Link, useLocation } from 'react-router-dom'

export default function Header() {
    const location = useLocation();

    return (
        <nav className="navbar navbar-expand-lg navbar-light bg-light">
            <div className="container-fluid">
                <Link className="navbar-brand" to="/">MERN CRUD Application</Link>
                <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span className="navbar-toggler-icon"></span>
                </button>
                <div className="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul className="navbar-nav me-auto mb-2 mb-lg-0">
                        <li className="nav-item">
                            <Link className={`nav-link ${location.path === '/' ? 'active' : '' }`} aria-current="page" to="/">
                            <i className="fas fa-home"></i> Home</Link>
                        </li>
                        <li className="nav-item">
                            <Link className={`nav-link ${location.path === '/create' ? 'active' : '' }`} to="/create">
                                <i className="fas fa-pencil"></i> Create</Link>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    )
}


Add task

Next inside the folder components add a new folder and name it 'tasks',  inside create a new component and name it 'Create.vue'. 

                                                            
                                                                                                                                
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom';
import axios from 'axios';

export default function Create() {
    const[title, setTitle] = useState('');
    const[description, setDescription] = useState('');
    const[error, setError] = useState('');
    const[loading, setLoading] = useState(false);
    const navigate = useNavigate();

    const createTask = async(e) => {
        e.preventDefault();
        setLoading(true);

        const task = { title, description};

        try {
            await axios.post('http://localhost:3001/tasks', task);
            setLoading(false);
            navigate('/');
        } catch (error) {
            setLoading(false);
            if(error.response.status === 422) {
                setError(error.response.data.message)
            }
            console.log(error);
        }
    }

    return (
        <div className='container'>
            <div className="row my-5">
                <div className="col-md-6 mx-auto">
                    {
                        error && <div className="alert alert-danger">
                            { error }
                        </div>
                    }
                    <div className="card">
                        <div className="card-header bg-white text-center">
                            <h6 className="mt-2">
                                Create new task
                            </h6>
                        </div>
                        <div className="card-body">
                            <form className="mt-5" onSubmit={(e) => createTask(e)}>
                                <div className="mb-2">
                                    <input type="text" name='title' 
                                        value={title}
                                        onChange={(e) => setTitle(e.target.value)}
                                        className="form-control" 
                                        placeholder='Title*'/>
                                </div>
                                <div className="mb-2">
                                    <textarea name="description" 
                                    cols="30" rows="5"
                                    value={description}
                                    onChange={(e) => setDescription(e.target.value)}
                                    placeholder='Description*'
                                    className='form-control'></textarea>
                                </div>
                                <div className="mb-2">
                                    {
                                        loading ? <div className="spinner-border" role="status">
                                                <span className="visually-hidden">Loading...</span>
                                            </div>
                                        :
                                        <button type="submit" className='btn btn-sm btn-primary'>
                                            Submit
                                        </button>
                                    }
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}


Edit task

Next inside the folder tasks create a new component and name it 'Edit.vue'. 

                                                            
                                                                                                                                
import React, { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom';
import axios from 'axios';


export default function Edit() {
    const[title, setTitle] = useState('');
    const[description, setDescription] = useState('');
    const[done, setDone] = useState(false);
    const[error, setError] = useState('');
    const[loading, setLoading] = useState(false);
    const navigate = useNavigate();
    const { id } = useParams();

    useEffect(() => {
        const fetchTask = async () => {
            try {
                const response = await axios.get(`http://localhost:3001/tasks/${id}`);
                setTitle(response.data.task.title);
                setDescription(response.data.task.description);
                setDone(response.data.task.done);
            } catch (error) {
                console.log(error);
            }
        }
        fetchTask();
    }, [id]);

    const editTask = async(e) => {
        e.preventDefault();
        setLoading(true);

        const task = { title, description, done};

        try {
            await axios.put(`http://localhost:3001/tasks/${id}`, task);
            setLoading(false);
            navigate('/');
        } catch (error) {
            setLoading(false);
            if(error.response.status === 422) {
                setError(error.response.data.message)
            }
            console.log(error);
        }
    }

    return (
        <div className='container'>
            <div className="row my-5">
                <div className="col-md-6 mx-auto">
                    {
                        error && <div className="alert alert-danger">
                            { error }
                        </div>
                    }
                    <div className="card">
                        <div className="card-header bg-white text-center">
                            <h6 className="mt-2">
                                Create new task
                            </h6>
                        </div>
                        <div className="card-body">
                            <form className="mt-5" onSubmit={(e) => editTask(e)}>
                                <div className="mb-2">
                                    <input type="text" name='title' 
                                        value={title}
                                        onChange={(e) => setTitle(e.target.value)}
                                        className="form-control" 
                                        placeholder='Title*'/>
                                </div>
                                <div className="mb-2">
                                    <textarea name="description" 
                                    cols="30" rows="5"
                                    value={description}
                                    onChange={(e) => setDescription(e.target.value)}
                                    placeholder='Description*'
                                    className='form-control'></textarea>
                                </div>
                                <div className="mb-2">
                                    <input type="checkbox"
                                        name='done'
                                        value={done}
                                        checked={done} 
                                        onChange={(e) => setDone(!done)}
                                        className="form-check-input" />
                                </div>
                                <div className="mb-2">
                                    {
                                        loading ? <div className="spinner-border" role="status">
                                                <span className="visually-hidden">Loading...</span>
                                            </div>
                                        :
                                        <button type="submit" className='btn btn-sm btn-primary'>
                                            Submit
                                        </button>
                                    }
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}


Create the Home component

Next, inside the components folder create a new file and name it 'Home.vue'.

                                                            
                                                                                                                                
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import { Link } from 'react-router-dom';

export default function Home() {
    const[loading, setLoading] = useState(false);
    const[tasks, setTasks] = useState([]);

    useEffect(() => {
        const fetchTasks = async() => {
            setLoading(true);
            try {
                const response = await axios.get('http://localhost:3001/tasks');
                setTasks(response.data.tasks);
                setLoading(false);
            } catch (error) {
                setLoading(false);
                console.log(error);
            }
        }
        fetchTasks();
    }, []);

    const deleteTask = async(id) => {
        try {
            const response = await axios.delete(`http://localhost:3001/tasks/${id}`);
            let updatedTasks = [...tasks];
            updatedTasks = tasks.filter(task => task._id !== id);
            setTasks(updatedTasks);
        } catch (error) {
            console.log(error);
        }
    }

    return (
        <div className='container'>
            <div className="row my-5">
                {
                    loading ? <div className="spinner-border" role="status">
                            <span className="visually-hidden">Loading...</span>
                        </div>
                    :
                    tasks?.map(task => (
                        <div className="col-md-4" key={task._id}>
                            <div className="card">
                                <div className="card-header bg-white text-center">
                                    <h6 className={`mt-2 ${task.done ? 'done' : ''}`}>
                                        { task.title.toUpperCase() }
                                    </h6>
                                </div>
                                <div className="card-body">
                                    <p className={`text-muted ${task.done ? 'done' : ''}`}>
                                        { task.description }
                                    </p>
                                </div>
                                <div className="card-footer bg-white d-flex justify-content-between">
                                    <Link to={`/edit/${task._id}`} className="btn btn-sm btn-warning">
                                        <i className="fas fa-edit"></i>
                                    </Link>
                                    <button className='btn btn-sm btn-danger'
                                        onClick={() => deleteTask(task._id)}>
                                        <i className="fas fa-trash"></i>
                                    </button>
                                </div>
                            </div>
                        </div>
                    ))
                }
            </div>
        </div>
    )
}


Add routes

Next, let's update the App component and add the routes.

Download the source code from here:

                                                            
                                                                                                                                
import Home from "./components/Home";
import Header from "./components/layouts/Header";
import Create from "./components/tasks/Create";
import Edit from "./components/tasks/Edit";
import { BrowserRouter, Routes, Route} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Header />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/create" element={<Create />} />
        <Route path="/edit/:id" element={<Edit />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;