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

如何用React Query在不频繁发送Mutation的情况下更新展示数据?

如何在React Query中实现本地临时修改,仅点击保存时发送Mutation?

近期开始使用React Query管理数据获取与缓存,但遇到了难题:如何在不频繁发送mutation的情况下更新展示的数据?

例如,在组件顶部通过查询获取用户列表,将所有用户展示在简易卡片中。此前使用React的useState时,会为输入框绑定handleFieldChange(id_user, field, value)函数,该函数仅更新指定用户的状态,点击保存按钮时再将更新后的用户数据发送至服务器。

以下是原useState实现的示例代码:

// react
import { useState, useEffect } from 'react';

// api
import { getUsers, updateUser } from '../api/users';

const UserCardGrid: React.FC = () => {
    const [users, setUsers] = useState([]);

    // handlers
    const handleGetUsers = async () => {
        const { data } = await getUsers();
        setUsers(data);
    };
    const handleFieldChange = (id_user: number, key: string, value: any) => {
        const newUsers = users.map((user: any) => {
            if (user.id_user === id_user) {
                return {
                    ...user,
                    [key]: value,
                };
            }
            return user;
        });
        setUsers(newUsers);
    };
    const handleUpdateUser = async (id_user: number) => {
        const user = users.find((user: any) => user.id_user === id_user);
        await updateUser(id_user, user);
    };

    // effects
    useEffect(() => {
        handleGetUsers();
    }, []);

    return (
        <div className='grid'>
            {users.map((user: any) => (
                <div key={user.id_user}>
                    <input
                        key={user.id_user}
                        type='text'
                        value={user.username}
                        onChange={(e) =>
                            handleFieldChange(user.id_user, 'username', e.target.value)
                        }
                    />

                    <button onClick={() => handleUpdateUser(user.id_user)}>Update</button>
                </div>
            ))}
        </div>
    );
};

export default UserCardGrid;

但使用React Query时,无法直接沿用这种方式。若要更新展示的文本,似乎每次输入都需更新缓存或发送useMutation请求。知道可以使用useMutation和乐观更新,但即使用防抖机制,每次输入都发送mutation也并不合理。

以下是React Query版本的待实现代码:

// api
import { useQuery, useMutation } from '@tanstack/react-query';
import { getUsers, updateUser } from '../api/users';

const UserCardGrid: React.FC = () => {
    const usersQ = useQuery(['users'], getUsers);
    const userM = useMutation(updateUser);

    // handlers
    const handleFieldChange = (id_user: number, key: string, value: any) => {
        // HOW COULD I IMPLEMENT THIS?
    };

    const handleUpdateUser = async (id_user: number) => {
        userM.mutate(id_user, usersQ.data.find((user: any) => user.id_user === id_user));
    };

    return (
        <div className='grid'>
            {usersQ.data?.map((user: any) => (
                <div key={user.id_user}>
                    <input
                        key={user.id_user}
                        type='text'
                        value={user.username}
                        onChange={(e) =>
                            handleFieldChange(user.id_user, 'username', e.target.value)
                        }
                    />
                    <button onClick={() => handleUpdateUser(user.id_user)}>Update</button>
                </div>
            ))}
        </div>
    );
};

export default UserCardGrid;

希望能自由修改用户数据,仅在点击保存按钮时再更新服务器数据。可以将用户数据存入state,但又不想放弃React Query返回页面时自动重新获取数据的特性。


方案一:本地状态 + React Query缓存同步

核心思路是用useState维护当前编辑的用户数据,同时监听React Query返回的data,当缓存更新(比如返回页面自动重新获取)时,同步更新本地状态。这样既保留了React Query的缓存和自动刷新特性,又能在本地自由修改,仅保存时发送请求。

实现代码:

import { useState, useEffect } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query';
import { getUsers, updateUser } from '../api/users';

const UserCardGrid: React.FC = () => {
    const usersQ = useQuery(['users'], getUsers);
    const [localUsers, setLocalUsers] = useState<any[]>([]);
    const updateUserMutation = useMutation({
        mutationFn: updateUser,
        // 成功后重新获取用户列表,保证缓存与服务器一致
        onSuccess: () => {
            usersQ.refetch();
        },
    });

    // 当React Query缓存更新时,同步到本地状态
    useEffect(() => {
        if (usersQ.data) {
            setLocalUsers(usersQ.data);
        }
    }, [usersQ.data]);

    const handleFieldChange = (id_user: number, key: string, value: any) => {
        setLocalUsers(prev => prev.map(user => 
            user.id_user === id_user ? { ...user, [key]: value } : user
        ));
    };

    const handleUpdateUser = (id_user: number) => {
        const updatedUser = localUsers.find(user => user.id_user === id_user);
        if (updatedUser) {
            updateUserMutation.mutate({ id_user, data: updatedUser });
        }
    };

    if (usersQ.isLoading) return <div>Loading...</div>;
    if (usersQ.isError) return <div>Error loading users</div>;

    return (
        <div className='grid'>
            {localUsers.map((user) => (
                <div key={user.id_user}>
                    <input
                        type='text'
                        value={user.username}
                        onChange={(e) =>
                            handleFieldChange(user.id_user, 'username', e.target.value)
                        }
                    />
                    <button 
                        onClick={() => handleUpdateUser(user.id_user)}
                        disabled={updateUserMutation.isLoading}
                    >
                        {updateUserMutation.isLoading ? 'Saving...' : 'Update'}
                    </button>
                </div>
            ))}
        </div>
    );
};

export default UserCardGrid;

方案二:直接操作React Query缓存(临时更新)

如果不想维护额外的本地状态,可以直接用React Query的setQueryData方法临时更新缓存中的数据,点击保存时发送Mutation,成功后重新获取数据保证一致性,失败则回滚缓存到之前的状态。

实现代码:

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { getUsers, updateUser } from '../api/users';

const UserCardGrid: React.FC = () => {
    const queryClient = useQueryClient();
    const usersQ = useQuery(['users'], getUsers);
    const updateUserMutation = useMutation({
        mutationFn: updateUser,
        // 乐观更新:发送请求前先更新缓存
        onMutate: async (updatedUser) => {
            // 取消当前可能的请求,避免竞态问题
            await queryClient.cancelQueries(['users']);
            // 保存当前缓存状态,用于失败回滚
            const previousUsers = queryClient.getQueryData(['users']);
            // 更新缓存中的用户数据
            queryClient.setQueryData(['users'], (oldUsers: any[]) => 
                oldUsers.map(user => 
                    user.id_user === updatedUser.id_user ? updatedUser.data : user
                )
            );
            // 返回回滚所需的上下文
            return { previousUsers };
        },
        // 请求失败时回滚缓存
        onError: (_err, _newUser, context) => {
            if (context?.previousUsers) {
                queryClient.setQueryData(['users'], context.previousUsers);
            }
        },
        // 请求完成后,重新获取用户列表保证数据一致
        onSettled: () => {
            queryClient.invalidateQueries(['users']);
        },
    });

    const handleFieldChange = (id_user: number, key: string, value: any) => {
        // 直接更新React Query缓存中的数据
        queryClient.setQueryData(['users'], (oldUsers: any[]) => 
            oldUsers.map(user => 
                user.id_user === id_user ? { ...user, [key]: value } : user
            )
        );
    };

    const handleUpdateUser = (id_user: number) => {
        const updatedUser = usersQ.data?.find(user => user.id_user === id_user);
        if (updatedUser) {
            updateUserMutation.mutate({ id_user, data: updatedUser });
        }
    };

    if (usersQ.isLoading) return <div>Loading...</div>;
    if (usersQ.isError) return <div>Error loading users</div>;

    return (
        <div className='grid'>
            {usersQ.data?.map((user) => (
                <div key={user.id_user}>
                    <input
                        type='text'
                        value={user.username}
                        onChange={(e) =>
                            handleFieldChange(user.id_user, 'username', e.target.value)
                        }
                    />
                    <button 
                        onClick={() => handleUpdateUser(user.id_user)}
                        disabled={updateUserMutation.isLoading}
                    >
                        {updateUserMutation.isLoading ? 'Saving...' : 'Update'}
                    </button>
                </div>
            ))}
        </div>
    );
};

export default UserCardGrid;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:04:58