React+MongoDB更新后页面未即时渲染问题求助(Context+Reducer)
问题:MongoDB更新后React无法即时渲染结果
已成功在MongoDB集群中更新对象,但浏览器无法即时渲染该更新结果,仅在刷新页面或再次执行更新函数后才会显示更新。使用React的Context和Reducer进行状态管理,以下是相关代码,求解决方案。
PlantDetails组件
import { usePlantsContext } from "../hooks/usePlantsContext"; import formatDistanceToNow from "date-fns/formatDistanceToNow"; import { useState } from "react"; import CalendarComponent from "./CalendarComponent"; const PlantDetails = ({ plant }) => { const [watered, setWatered] = useState(false) const [newWaterDate, setNewWaterDate] = useState("") const { dispatch } = usePlantsContext(); const handleClick = async () => { const response = await fetch("/api/plants/" + plant._id, { method: "DELETE", }); const json = await response.json(); if (response.ok) { dispatch({ type: "DELETE_PLANT", payload: json }); } }; const updatePlant = async (e) => { e.preventDefault() plant.nextWaterDate = newWaterDate const response = await fetch("api/plants/" + plant._id, { method: "PATCH", body: JSON.stringify(plant), headers: { 'Content-Type': 'application/json' } }) const json = await response.json() if(response.ok) { dispatch({ type: "UPDATE_PLANT", payload: json }) } console.log('updated') setWatered(false) } return ( <div className="plant-details"> <h4>{plant.plantName}</h4> <p>{plant.quickInfo}</p> <p> {formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })} </p> <span onClick={handleClick}>delete</span> <div> <p>next water date: {plant.nextWaterDate}</p> <input onChange={(e) => setNewWaterDate(e.target.value)}/> <button onClick={updatePlant}>update</button> <input value={watered} type="checkbox" id="toWater" onChange={() => setWatered(true)}/> <label for="toWater">watered</label> {watered && <CalendarComponent updatePlant={updatePlant} setNextWaterDate={setNewWaterDate}/>} </div> </div> ); }; export default PlantDetails;
Context代码
import { createContext, useReducer } from 'react' export const PlantsContext = createContext() export const plantsReducer = (state, action) => { switch(action.type) { case 'SET_PLANTS': return { plants: action.payload } case 'CREATE_PLANT': return { plants: [action.payload, ...state.plants] } case 'DELETE_PLANT': return { plants: state.plants.filter((p) => p._id !== action.payload._id) } case 'UPDATE_PLANT': return { plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p ) } default: return state } } export const PlantsContextProvider = ({ children }) => { const [state, dispatch] = useReducer(plantsReducer, { plants: null }) return ( <PlantsContext.Provider value={{...state, dispatch}}> { children } </PlantsContext.Provider> ) }
plantController(更新逻辑)
const updatePlant = async (req, res) => { const { id } = req.params if(!mongoose.Types.ObjectId.isValid(id)) { return res.status(404).json({ error: "No plant" }) } const plant = await Plant.findByIdAndUpdate({ _id: id }, { ...req.body }) if (!plant) { return res.status(400).json({ error: "No plant" }) } res.status(200) .json(plant) }
Home组件
import { useEffect } from "react"; import PlantDetails from "../components/PlantDetails"; import PlantForm from "../components/PlantForm"; import CalendarComponent from "../components/CalendarComponent"; import { usePlantsContext } from "../hooks/usePlantsContext"; const Home = () => { const { plants, dispatch } = usePlantsContext(); useEffect(() => { const fetchPlants = async () => { console.log("called"); // ONLY FOR DEVELOPMENT! const response = await fetch("/api/plants"); const json = await response.json(); if (response.ok) { dispatch({ type: "SET_PLANTS", payload: json }); } }; fetchPlants(); }, [dispatch]); return ( <div className="home"> <div className="plants"> {plants && plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)} </div> <PlantForm /> </div> ); }; export default Home;
解决方案
1. 后端返回更新后的最新文档
findByIdAndUpdate默认返回更新前的旧文档,导致前端拿到的payload还是旧数据,Context状态未真正更新。需添加{ new: true }选项让Mongoose返回更新后的文档:
const plant = await Plant.findByIdAndUpdate({ _id: id }, { ...req.body }, { new: true }) // 新增该选项
2. 前端避免直接修改props对象
直接修改传入的plant props违反React不可变数据原则,会污染原始数据。应创建新对象传递给接口:
const updatePlant = async (e) => { e.preventDefault() // 创建新对象,不修改原props const updatedPlant = { ...plant, nextWaterDate: newWaterDate } const response = await fetch("/api/plants/" + plant._id, { // 补全路径斜杠,避免相对路径错误 method: "PATCH", body: JSON.stringify(updatedPlant), headers: { 'Content-Type': 'application/json' } }) const json = await response.json() if(response.ok) { dispatch({ type: "UPDATE_PLANT", payload: json }) } console.log('updated') setWatered(false) }
3. 验证状态更新逻辑
Reducer中的UPDATE_PLANT逻辑是正确的,通过map替换对应ID的对象。只要payload是最新文档,就能正确更新Context中的plants数组,触发组件重新渲染。
完成以上修改后,更新操作会返回最新文档,dispatch更新Context状态,PlantDetails组件将即时渲染新的nextWaterDate值。
内容的提问来源于stack exchange,提问作者fluffy-lionz
相关产品推荐
相关产品推荐

