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

React结合JSON-server使用PATCH方法更新数据的状态同步问题

问题

出于学习目的,我正在使用React和JSON-server开发一个CRUD待办清单。目前在使用PATCH方法时遇到问题:仅首次点击能更新JSON-server中的数据,后续点击无法同步。React组件内的状态能正常标记待办项为已完成,但希望服务器端的待办项状态也能同步更新。

请求服务文件

const serverAddress = 'http://localhost:8000';
const collection = 'todoItems';

const fetchAll = async () => {
  const response = await fetch(`${serverAddress}/${collection}`);
  const todoItems = response.json();

  return todoItems;
};

const complete = async (id) => {
const completed = {
  completed : true
}

// how to set the 'completed' value in json-server based on item's state? 

const response = await fetch(`${serverAddress}/${collection}/${id}`, {
  method: 'PATCH',
  headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: JSON.stringify(completed ),
  });

 const data = await response.json();

 return data;
}

const TodoItemsService = {
  fetchAll,
  create,
  remove,
  complete
};

export default TodoItemsService;

Card组件(承载所有待办项)

const Card = () => {
const [todoItems, setTodoItems] = useState([]);

const fetchAllTodoItems = async () => {
  const fetchedTodoItems = await TodoItemsService.fetchAll();
  setTodoItems(fetchedTodoItems);
};

useEffect(() => {
  (async () => {
    fetchAllTodoItems();
  })();
}, []);

const handleComplete = async (id) => {
  await TodoItemsService.complete(id);
}

return (
  <div className='card'>
    <CardHeader />
    <AddTodoForm onAddTodoItem={handleAddTodoItem} />
    <TodoItemsContainer
      todoItems={todoItems}
      onDelete={handleDelete}
      onComplete={handleComplete}
    />
  </div>
 )
}

export default Card;

TodoItemsContainer组件

const TodoItemsContainer = ({ todoItems, onDelete, onComplete }) => {

return (
  <div className='todo-items-container'>
    {todoItems.length === 0 &&
      <div className='empty'>
        <img src={NoTodoItems} alt="" />
      </div>}
    {todoItems.map(({ id, text }) => (
      <TodoItem
        key={id}
        id={id}
        text={text}
        onDelete={onDelete}
        onComplete={onComplete}
      />
    ))}
  </div>
 )
}

export default TodoItemsContainer;

TodoItem组件

const TodoItem = ({ id, text, onDelete, onComplete }) => {
const [isComplete, setIsComplete] = useState(false);

const handleIsCompleteById = () => {
  onComplete(id);
  setIsComplete(!isComplete);
};

const handleDeleteTodoItemById = () => {
  onDelete(id);
};

return (
  <div className={`todo-item ${isComplete ? 'complete' : ''}`}>
    <p>{text}</p>
  <div>
     <TodoItemComplete onComplete={handleIsCompleteById}/>
     <TodoItemDelete onDelete={handleDeleteTodoItemById}/>
   </div>
  </div>
 )
}

export default TodoItem;

TodoItemComplete按钮组件

const TodoItemComplete = ({ onComplete }) => {

return (
  <button type='button' onClick={onComplete}>
    <div className='icon'>
      {<SVGComplete />}
    </div>
  </button>
  )
}

export default TodoItemComplete;

问题分析与解决建议

核心问题1:PATCH接口固定设置completed: true,无法切换状态

你的complete接口每次都强制把待办项的completed设为true,首次点击后服务器端该字段已为true,后续调用PATCH时,JSON Server因为字段值无变化不会执行更新,但前端仍在切换isComplete状态,导致前后端状态脱节。

解决方法:
修改complete函数,支持切换状态,有两种实现方式:

  1. 前端传递目标状态给接口:
// 请求服务文件的complete函数修改为
const complete = async (id, completedState) => {
  const completed = {
    completed: completedState
  };

  const response = await fetch(`${serverAddress}/${collection}/${id}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: JSON.stringify(completed),
  });

  const data = await response.json();
  return data;
};

更新TodoItem的点击逻辑,传递目标状态:

const handleIsCompleteById = () => {
  const newState = !isComplete;
  onComplete(id, newState);
  setIsComplete(newState);
};

同步更新Card组件的handleComplete:

const handleComplete = async (id, completedState) => {
  await TodoItemsService.complete(id, completedState);
};
  1. 接口内先获取当前状态再切换:
    如果不想依赖前端传递状态,可在接口内先拉取待办项当前状态,再反转completed值:
const complete = async (id) => {
  // 先获取当前待办项数据
  const currentItem = await fetch(`${serverAddress}/${collection}/${id}`).then(res => res.json());
  const completed = {
    completed: !currentItem.completed
  };

  const response = await fetch(`${serverAddress}/${collection}/${id}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: JSON.stringify(completed),
  });

  const data = await response.json();
  return data;
};

核心问题2:前端未同步服务器最新数据

当前handleComplete调用接口后,没有更新Card组件中的todoItems状态,导致下次渲染仍使用旧数据(除非刷新页面)。虽然TodoItem自身维护了状态,但如果其他组件依赖Card的todoItems,会出现不一致。

解决方法:
调用接口后,选择以下方式更新前端状态:

  • 方式1:重新拉取所有数据(简单直接)
const handleComplete = async (id, completedState) => {
  await TodoItemsService.complete(id, completedState);
  fetchAllTodoItems(); // 调用已有的拉取函数更新状态
};
  • 方式2:直接修改本地状态(性能更优)
const handleComplete = async (id, completedState) => {
  await TodoItemsService.complete(id, completedState);
  setTodoItems(prev => prev.map(item => 
    item.id === id ? {...item, completed: completedState} : item
  ));
};

额外优化:TodoItem初始状态与服务器同步

当前TodoItem的isComplete初始值固定为false,如果服务器中已有完成状态的待办项,前端会显示错误状态。需要从服务器数据中获取初始状态:

  1. 修改TodoItemsContainer的映射逻辑,传递completed字段:
{todoItems.map(({ id, text, completed }) => (
  <TodoItem
    key={id}
    id={id}
    text={text}
    completed={completed} // 新增传递该字段
    onDelete={onDelete}
    onComplete={onComplete}
  />
))}
  1. 修改TodoItem组件,用传入的completed作为初始状态:
const TodoItem = ({ id, text, completed, onDelete, onComplete }) => {
const [isComplete, setIsComplete] = useState(completed); // 替换固定初始值

// 其余逻辑不变
}

内容的提问来源于stack exchange,提问作者kkkjjj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 06:26:28