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

PATCH请求后状态无法实时更新:useReducer与Context问题咨询

问题描述

我已经成功把plant对象更新到集群里,但必须刷新页面才能拿到更新后的数据。我猜可能需要用useEffect重新调用fetch,但不知道怎么在PATCH请求完成后执行。求更新后自动获取最新数据的实现建议。


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

PlantDetails组件中的更新函数

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

PATCH请求后需更新的Home组件

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;

usePlantsContext Hook

import { PlantsContext } from "../context/PlantContext";
import { useContext } from "react";

export const usePlantsContext = () => {
    const context = useContext(PlantsContext)
    if(!context) {
        throw Error('usePlantsContext must be used inside an PlantsContext Provider')
    }
    return context
}

完整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 })
    }
    // 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 type="checkbox" id="toWater" onChange={() => setWatered(true)}/> */}
        <label value={watered} for="toWater">watered</label>
        <CalendarComponent setNextWaterDate={setNewWaterDate}/>
      </div>
      <button onClick={updatePlant}>update</button>
    </div>
  );
};
export default PlantDetails;

Plant Controller 后端代码

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

解决方案

核心问题分析

你的代码已经在PATCH成功后触发了UPDATE_PLANT的dispatch,理论上Context状态更新后Home组件应该自动重新渲染,但没生效的原因主要有两个:

  1. 后端返回旧数据:findByIdAndUpdate默认返回更新前的文档,前端拿到旧数据更新state,自然看不到变化。
  2. 直接修改Props对象:plant.nextWaterDate = newWaterDate直接修改传入的props,可能导致React状态对比失效,无法触发重渲染。

具体修复步骤

  1. 修正后端返回更新后的数据
    修改Plant Controller中的updatePlant函数,添加{new: true}选项,让Mongoose返回更新后的文档:

    const updatePlant = async (req, res) => {
        const { id } = req.params
        if(!mongoose.Types.ObjectId.isValid(id)) {
            return res.status(404).json({ error: "No plant" })
        }
    
        // 添加{new: true}返回更新后的文档
        const plant = await Plant.findByIdAndUpdate({ _id: id }, {
            ...req.body
        }, { new: true })
    
        if (!plant) {
            return res.status(400).json({ error: "No plant" })
        }
        res.status(200).json(plant)
    }
    
  2. 避免直接修改Props对象
    在PlantDetails的updatePlant函数中,创建新的对象而非直接修改原props:

    const updatePlant = async (e) => {
        e.preventDefault()
        // 复制原plant对象并修改属性,不影响原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 })
          // 可选:清空日期选择器
          setNewWaterDate("")
        }
      }
    
  3. 确认Context更新触发渲染
    Home组件已经通过usePlantsContext订阅了状态,当plants数组更新时,React会自动重新渲染PlantDetails列表,无需额外调用fetch刷新。如果仍有问题,检查:

    • UPDATE_PLANT的reducer逻辑是否正确:你的reducer用map替换对应ID的元素,逻辑无误。
    • 所有相关组件是否包裹在PlantsContextProvider内部。

备选方案:强制重新拉取数据(不推荐)

如果上述方法无法解决问题,可在PATCH成功后手动拉取全量数据更新状态(会多一次请求,效率较低):

// 在PlantDetails的updatePlant函数中添加
if(response.ok) {
  dispatch({ type: "UPDATE_PLANT", payload: json })
  // 手动拉取最新列表
  const fetchResponse = await fetch("/api/plants")
  const fetchJson = await fetchResponse.json()
  if(fetchResponse.ok) {
    dispatch({ type: "SET_PLANTS", payload: fetchJson })
  }
}

内容的提问来源于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.13 20:30:52