node.js - 当 props.match.params.id 返回未定义但 id 在 url 上可见时,如何获取 url 的 id 参数?

标签 node.js reactjs mongodb express mern

我第一次使用 MERN 堆栈构建一个简单的待办事项应用程序,我的 Express 服务器工作正常,我能够使用 postman 在 mongodb 上执行 CRUD 操作,但是使用 React 前端我只能执行 CR 但无法更新。我还没有编写删除部分的代码。前端带有react router dom,只有3个链接,即通过axios api调用渲染todos的“主页”。用于创建新待办事项的“添加”页面和单击链接按钮时将 id 作为 Prop 从待办事项页面传递的“编辑”页面。

这是我的代码。

Express 服务器

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};

自定义 REACT Hook 来获取数据

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 主页

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

添加待办事项页面

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

编辑待办事项页面

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;

带有 id 参数的自定义 REACT Hook - 由上面的编辑页面使用。

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

正如您在编辑页面中看到的那样,表单没有填充数据,而我可以从待办事项页面获取作为链接传递的 id 参数;我无法获取数据,这是带有 mongodb 对象 ID 的 url 的样子:

enter image description here

我该如何解决这个问题? 谢谢!

最佳答案

您必须导入这些import { withRouter } from "react-router-dom" 并将它们传递到 export default withRouter(EditTodo);

关于node.js - 当 props.match.params.id 返回未定义但 id 在 url 上可见时,如何获取 url 的 id 参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56574514/

相关文章:

node.js - 如何从日志消息调试node.js程序

node.js - Ember : The response to store. 查询应该是一个数组,但它是一条记录错误

mongodb - 为什么 Mongo Spark 连接器为查询返回不同且不正确的计数?

ruby-on-rails - MongoId 与哈希字段 : values are different with MongoDB and with Rails

node.js - 查询时 mongodb native 驱动程序错误

reactjs - 失败的 PropType。值未定义

reactjs - 我如何在 react 中制作这样的组件?

javascript - 需要通过代码的执行来获取Javascript中的输出

mongodb - 使用 mgo.Monotonic 从中学读取

node.js - Bootstrap 4 导航栏折叠溢出。 Express/Node 部分