You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 22:35:38