Twirlman
Twirlman

Reputation: 1189

How to get the id parameter of a url when the props.match.params.id returns undefined but the id is visible on the url?

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;

enter image description here

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;

enter image description here

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;

enter image description here

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:

enter image description here

How do i solve this? THANKS!

Upvotes: 1

Views: 2180

Answers (1)

Salman Mehmood
Salman Mehmood

Reputation: 283

You must import these import { withRouter } from "react-router-dom" and pass these in export default withRouter(EditTodo);

Upvotes: 1

Related Questions