Reputation: 1189
I'm building a simple todo app using the MERN stack for the first time, my express server is working fine and i'm able to do the CRUD operations on mongodb with postman, however with the react front-end i can only do the CR but unable to update. I haven't coded the delete section yet. The front-end comes with react router dom with only 3 links, the "homepage" where todos are rendered through axios api call. The "add" page to create new todo and the "edit" page where the id is passed on as props from the todo page on a click of a link button.
Here's my code.
Express Server
const express = require("express");
const todoRoutes = express.Router();
const cors = require("cors");
const path = require("path");
const port = 4000;
const db = require("./db");
db.connect((err)=>{
if(err){
console.log("unable to connect to database");
process.exit(1);
} else {
console.log("connected to the database");
}
})
const app = express();
app.use(cors());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
/* Get list of Todos */
todoRoutes.route('/').get((req,res)=>{
db.getDB().collection("todos").find({}).toArray((err,docs)=>{
if(err)
console.log(err);
else {
console.log(docs);
res.json(docs);
}
});
});
/* Get Todo */
todoRoutes.route('/:id').get((req,res)=>{
let todoID = req.params.id;
db.getDB().collection("todos").find({_id: db.getPrimaryKey(todoID)}).toArray((err,docs)=>{
if(err)
console.log(err);
else {
console.log(docs);
res.json(docs);
}
});
});
/* Create Todo */
todoRoutes.route('/create').post((req,res)=>{
const userInput = req.body;
db.getDB().collection("todos").insertOne({description:userInput.description,responsible:userInput.responsible,priority:userInput.priority,completed:false},(err,docs)=>{
if(err)
console.log(err);
else{
res.json(docs);
}
});
});
/* Edit todo */
todoRoutes.route('/edit/:id').get((req,res)=>{
let todoID = req.params.id;
db.getDB().collection("todos").find({_id: db.getPrimaryKey(todoID)}).toArray((err,docs)=>{
if(err)
console.log(err);
else {
console.log(docs);
res.json(docs);
}
});
});
todoRoutes.route('/edit/:id').put((req,res)=>{
const todoID = req.params.id;
const userInput = req.body;
db.getDB().collection("todos").updateOne({_id: db.getPrimaryKey(todoID)},{$set:{description:userInput.description,responsible:userInput.responsible,priority:userInput.priority,completed:false}},{returnOrignal:false},(err,docs)=>{
if(err)
console.log(err);
else
res.json(docs)
});
});
/* Delete todo */
todoRoutes.route('/delete/:id').delete((req,res)=>{
const todoID = req.params.id;
db.getDB().collection("todos").deleteOne({_id: db.getPrimaryKey(todoID)},(err,docs)=>{
if(err)
console.log(err)
else{
res.json(docs);
}
});
});
app.use('/',todoRoutes);
app.listen(port,()=>{
console.log(`Server listening to port ${port}`);
});
MongoDB Database
const MongoClient = require("mongodb").MongoClient;
const ObjectID = require("mongodb").ObjectID;
const url = "actual_url_not_posted_for_security_reasons";
const dbName = "mernstack";
const client = new MongoClient(url,{useNewUrlParser:true});
const state = {
db: null
}
const connect = (cb) =>{ /* callback */
/* if there's a database connection */
if(state.db){
cb();
} else {
client.connect(function(err){
if(err){
cb(err);
} else {
state.db = client.db(dbName);
cb();
}
});
}
}
/* Get the primary key based on the object id */
const getPrimaryKey = (_id)=>{
return ObjectID(_id);
}
/* Get the database */
const getDB = ()=>{
return state.db;
}
module.exports = { getDB, connect, getPrimaryKey};
Custom REACT Hook to fetch data
import {useState,useEffect} from 'react';
import axios from 'axios';
const useGetAPI = (url)=>{
const [data,setData] = useState([]);
useEffect(()=>{
const fetchData = async ()=>{
const response = await axios.get(url);
const data = [...response.data];
const error = response.error;
if(error)
console.log(error)
else{
console.log(data);
setData(data);
}
};
fetchData();
},[url])
return data;
}
export default useGetAPI;
TODOS Homepage
import React from 'react';
import useGetAPI from '../custom_hooks/useGetAPI';
import Todo from './todo_item/Todo';
const Todos = () =>{
const data = useGetAPI('http://localhost:4000');
return (
<div className="page">
<div className="page-header">
<h1>Todo Lists</h1>
</div>
<div className="page-content">
<ul className="todo-list">
{
data.map((todo)=><Todo todo={todo} key={todo._id}/>)
}
</ul>
</div>
</div>
);
}
export default Todos;
ADD todo page
import React,{useState, useEffect, useCallback} from 'react';
import { Redirect } from 'react-router-dom';
import {FaTelegramPlane} from 'react-icons/fa';
import axios from 'axios';
const AddTodo = () =>{
const [description,setDescription] = useState('');
const [responsible,setResponsible] = useState('');
const [priority,setPriority] = useState('');
const [completed,setCompleted] = useState(false);
const [redirect,setRedirect] = useState(false);
const handleDescription = useCallback((e)=>{
setDescription(e.target.value);
},[setDescription]);
const handleResponsible = useCallback((e)=>{
setResponsible(e.target.value);
},[setResponsible]);
const handlePriority = useCallback((e)=>{
setPriority(e.target.value);
},[setPriority]);
const handleSubmit = useCallback((e)=>{
e.preventDefault();
const newTodo = {
description,
responsible,
priority,
completed: false
}
axios.post('http://localhost:3001/create',newTodo)
.then(res=>{
console.log(res.data);
setRedirect(!redirect);
})
.catch(function (error) {
console.log(error);
});
clearInputs();
},[description,responsible,priority,redirect,setRedirect]);
useEffect(()=>{
/* default state of todo */
displayStatus();
return ()=>{
/* after submit state of todo */
displayStatus();
}
});
const clearInputs = ()=>{
/* Clear inputs */
//setTodos([]);
setDescription('');
setResponsible('');
setPriority('');
setCompleted(false);
}
const displayStatus = ()=>{
console.log(`
Description: ${description}
Responsible: ${responsible}
Priority: ${priority}
Completed: ${completed}`);
}
return (
/* If the form submits, the redirect state is updated and redirects to homepage. */
redirect? <Redirect to="/" /> :
<div className="page">
<div className="page-header">
<h1>Create New Todo</h1>
</div>
<div className="page-content">
<form id="add-todo-form" className="todo-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="todo_description">Description:</label>
<input id="todo_description" type="text" className="form-control" value={description} onChange={handleDescription} />
</div>
<div className="form-group">
<label htmlFor="todo_responsible">Responsible:</label>
<input id="todo_responsible" type="text" className="form-control" value={responsible} onChange={handleResponsible} />
</div>
<div className="form-group">
<label htmlFor="todo_priorities">Priorities:</label>
<div id="todo_priorities" className="form-radios">
<label htmlFor="radio1" className="radio-label">
<input name="priorityOptions" type="radio" id="radio1" value="Low" checked={priority === 'Low'} onChange={handlePriority}/>
<span className="radiomark"></span>
<span className="radiotext">Low</span>
</label>
<label htmlFor="radio2" className="radio-label">
<input type="radio" id="radio2" value="Medium" checked={priority === 'Medium'} onChange={handlePriority}/>
<span className="radiomark"></span>
<span className="radiotext">Medium</span>
</label>
<label htmlFor="radio3" className="radio-label">
<input type="radio" id="radio3" value="High" checked={priority === 'High'} onChange={handlePriority}/>
<span className="radiomark"></span>
<span className="radiotext">High</span>
</label>
</div>
</div>
<div className="form-group">
<button type="submit" className="form-btn"><FaTelegramPlane />Submit</button>
</div>
</form>
</div>
</div>
);
}
export default AddTodo;
Edit todo page
import React,{useState, useEffect, useContext, useCallback} from 'react';
import useGetApiWithParams from '../custom_hooks/useGetApiWithParams';
import {FaTelegramPlane} from 'react-icons/fa';
import axios from 'axios';
const EditTodo = (props) =>{
const data = useGetApiWithParams('http://localhost:4000/edit',props.match.params.id);
console.log(props.match.params.id);
/* Set default data from database */
const [description,setDescription] = useState('');
const [responsible,setResponsible] = useState('');
const [priority,setPriority] = useState('');
const [completed,setCompleted] = useState(false);
const handleDescription = useCallback((e)=>{
setDescription(e.target.value);
},[setDescription]);
const handleResponsible = useCallback((e)=>{
setResponsible(e.target.value);
},[setResponsible]);
const handlePriority = useCallback((e)=>{
setPriority(e.target.value);
},[setPriority]);
const handleCompleted = useCallback((e)=>{
setCompleted(!completed);
},[completed,setCompleted])
const handleSubmit = useCallback((e)=>{
e.preventDefault();
console.log('Form submitted');
console.log(`Description ${description}`);
console.log(`Description ${responsible}`);
console.log(`Description ${priority}`);
console.log(`Description ${completed}`);
const updatedTodo = {
description,
responsible,
priority,
completed: false
}
axios.put(`http://localhost/4000/edit/${props.match.params.id}`, updatedTodo)
.then(res=>console.log(res.data))
.catch(function (error) {
console.log(error);
});
},[description,responsible,priority,completed,props.match.params.id]);
return (
<div className="page">
<div className="page-header">
<h1>Edit Todo</h1>
</div>
<div className="page-content">
<form id="edit-todo-form" className="todo-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="description">Description:</label>
<input id="description" type="text" className="form-control" onChange={handleDescription} value={description}/>
</div>
<div className="form-group">
<label htmlFor="responsible">Responsible:</label>
<input id="responsible" type="text" className="form-control" onChange={handleResponsible} value={responsible}/>
</div>
<div className="form-group">
<label htmlFor="priorities">Priorities:</label>
<div id="priorities" className="form-radios">
<label htmlFor="radio1" className="radio-label">
<input name="priorityOptions" type="radio" id="radio1" value="Low" checked={priority === 'Low'} onChange={handlePriority}/>
<span className="radiomark"></span>
<span className="radiotext">Low</span>
</label>
<label htmlFor="radio2" className="radio-label">
<input type="radio" id="radio2" value="Medium" checked={priority === 'Medium'} onChange={handlePriority}/>
<span className="radiomark"></span>
<span className="radiotext">Medium</span>
</label>
<label htmlFor="radio3" className="radio-label">
<input type="radio" id="radio3" value="High" checked={priority === 'High'} onChange={handlePriority}/>
<span className="radiomark"></span>
<span className="radiotext">High</span>
</label>
</div>
</div>
<div className="form-group">
<label htmlFor="todo_completed">Status:</label>
<div id="todo_completed">
<label htmlFor="checkcompleted" className="check-label">
<input type="checkbox" id="checkcompleted" value={completed} onChange={handleCompleted}/>
<span className="checkmark"></span>
<span className="checktext">Completed</span>
</label>
</div>
</div>
<div className="form-group">
<button type="submit" className="form-btn"><FaTelegramPlane />Save Changes</button>
</div>
</form>
</div>
</div>
);
}
export default EditTodo;
Custom REACT Hook with id parameter - Used by the edit page above.
import {useState,useEffect} from 'react';
import axios from 'axios';
const useGetApiWithParams = (url,params)=>{
const [data,setData] = useState([]);
useEffect(()=>{
const fetchData = async ()=>{
const response = await axios.get(`${url}/${params}`);
const data = [...response.data];
const error = response.error;
if(error)
console.log(`Error: ${error}`)
else{
console.log(data);
setData(data);
}
};
fetchData();
},[url,params])
return data;
}
export default useGetApiWithParams;
As you can see in the edit page the form isn't filled with data, while I'm able to get the id parameter passed on as a link from the todo page; i'm unable to fetch the data, here's how the url looks like with the mongodb's object id:
How do i solve this? THANKS!
Upvotes: 1
Views: 2180
Reputation: 283
You must import these import { withRouter } from "react-router-dom"
and pass these in export default withRouter(EditTodo)
;
Upvotes: 1