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

useSWR获取数据后修改未保存,返回页面仍显修改值的问题及最佳实践

问题描述

在React中使用useSWR获取数据后,直接修改了返回对象的title字段。未保存数据就导航离开再返回页面时,虽然控制台显示已获取到正确数据,但useSWR仍返回之前修改后的对象,所有调用该useSWR的地方都显示修改后的值。想知道使用useSWR的最佳实践,是否应该复制数据后仅修改副本?

问题代码示例

import { useRouter } from "next/router";

import type { NextPage } from 'next';
import offerService from "services/offer.service";
import useSWR from "swr";
import Offer from "types/offer.type";
import { Box, TextField, Typography } from "@mui/material";

const EditOfferPage: NextPage = () => {
  const router = useRouter();
  
  const { offerId } = router.query;

  // 使用SWR加载offer对象
  const { data: offer, error } = useSWR<Offer>(
    "getOfferById", () => offerService.getOfferById(parseInt(String(offerId))).then((res) => res.data)
  );
  
  if (error) return <Typography>Error</Typography>;
  if (!offer) return <Typography>Loading...</Typography>;

  const handleChange = (event:  React.ChangeEvent<HTMLInputElement>) => {
    // 直接修改offer对象的title
    offer.title = event.target.value;
  }

  return (
    <Box>
      <TextField
            id="test-textfield"
            value={offer.title}
            onChange={handleChange}
          />
    </Box>
  );
};

export default EditOfferPage;

问题原因

useSWR会缓存获取到的数据,你直接修改的offer对象是useSWR缓存的引用。当导航离开再返回时,useSWR优先返回缓存中的修改后对象;即便重新请求拿到新数据,由于对象引用未变,React可能不会触发重新渲染,最终导致所有依赖该缓存的地方都显示旧的修改值。


最佳实践与解决方案

是的,永远不要直接修改useSWR返回的原始数据,必须复制数据后修改副本。以下是两种常用方案:

方案1:用本地状态管理表单临时数据

把useSWR返回的原始数据作为初始值存入useState,后续修改仅针对本地状态,完全隔离缓存数据:

import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import type { NextPage } from 'next';
import offerService from "services/offer.service";
import useSWR from "swr";
import Offer from "types/offer.type";
import { Box, TextField, Typography } from "@mui/material";

const EditOfferPage: NextPage = () => {
  const router = useRouter();
  const { offerId } = router.query;

  const { data: originalOffer, error } = useSWR<Offer>(
    "getOfferById", () => offerService.getOfferById(parseInt(String(offerId))).then((res) => res.data)
  );
  
  // 用useState保存可编辑的副本
  const [offer, setOffer] = useState<Offer | null>(null);

  // 原始数据更新时同步到本地状态
  useEffect(() => {
    if (originalOffer) {
      // 层级简单的对象用浅拷贝,复杂对象用深拷贝(如JSON.parse(JSON.stringify())或lodash.cloneDeep)
      setOffer({...originalOffer});
    }
  }, [originalOffer]);

  if (error) return <Typography>Error</Typography>;
  if (!offer) return <Typography>Loading...</Typography>;

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    // 修改本地状态副本,不影响useSWR缓存
    setOffer(prev => prev ? {...prev, title: event.target.value} : null);
  }

  return (
    <Box>
      <TextField
        id="test-textfield"
        value={offer.title}
        onChange={handleChange}
      />
    </Box>
  );
};

export default EditOfferPage;

方案2:用useSWR的mutate更新缓存(需同步全局数据时用)

如果需要让其他组件也看到修改后的数据,可以用mutate方法,但同样要基于原始数据创建副本后再更新缓存:

import { useRouter } from "next/router";
import type { NextPage } from 'next';
import offerService from "services/offer.service";
import useSWR from "swr";
import Offer from "types/offer.type";
import { Box, TextField, Typography } from "@mui/material";

const EditOfferPage: NextPage = () => {
  const router = useRouter();
  const { offerId } = router.query;

  const { data: offer, error, mutate } = useSWR<Offer>(
    "getOfferById", () => offerService.getOfferById(parseInt(String(offerId))).then((res) => res.data)
  );
  
  if (error) return <Typography>Error</Typography>;
  if (!offer) return <Typography>Loading...</Typography>;

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    // 创建副本并修改
    const updatedOffer = {...offer, title: event.target.value};
    // 更新缓存,false表示不触发重新请求
    mutate(updatedOffer, false);
  }

  return (
    <Box>
      <TextField
        id="test-textfield"
        value={offer.title}
        onChange={handleChange}
      />
    </Box>
  );
};

export default EditOfferPage;

核心原则

  • 禁止直接修改useSWR返回的data对象,避免污染全局缓存;
  • 始终操作数据副本,根据场景选择本地状态或缓存更新;
  • 临时表单状态优先用useState管理,需同步全局数据时再用mutate。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 17:40:24