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

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

url-with-params

How do i solved this? THANKS!

const port = 4000;
url: localhost:3000/edit/…
What happens if you initialize const port as 3000?

@ptrvenckus My express server runs on port 4000 while the client (react front-end) runs on 3000, i have added a “proxy”: “http://localhost:4000” on my client’s package.json

how did you solve it??

1 Like

I think i have use another fetch data using the id in the url as a parameter added to the api link.