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

React表单提交后如何无刷新更新后端数据列表

问题:创建新条目后需刷新页面才能显示,实现无刷新更新列表

我的页面通过API从后端获取数据,以列表区块形式展示文本和日期,当前功能正常。页面设有「添加新条目」按钮,点击后弹出带文本输入框和日期选择框的模态框,提交后数据会发送到后端创建新条目,但必须刷新页面才能看到新条目,需要实现无刷新即时展示。

我已在模态框的onSuccess中触发状态更新:

const modal = (title, onSuccess) => {
    openModal(AddActivityModal, {
        onClose: closeModal,
        title: title,
        onSuccess: async () => {
            closeModal();
            setActivityDataUpdated(!activityDataUpdated);
        },
    })
}

需要在onSuccess时重新调用useActivities(props.status)获取数据,但尝试的写法违反了Hook规则:

const { data, isFetching, activityDataUpdated } = useActivities(props.status); 
useEffect(() => {
    if (activityDataUpdated) {
        useActivities(props.status);
    }
}, [activityDataUpdated]);

以下是相关代码:

ActivityPage.js

import React, {useState} from "react";
import AppLayout from "../../layout/AppLayout";
import Activities from "../activity/Activities";
import ActivityDetails from "../activity/ActivityDetails";
import ActivityDetailsNoData from "../activity/ActivityDetailsNoData";
import AddActivityModal from "../modals/AddActivityModal";
import {useModal} from "../../contexts/ModalContext";

function ActivityPage(props) {
    const [activityId, setActivityId] = useState(null);
    const { closeModal, openModal } = useModal();
    const [activityDataUpdated, setActivityDataUpdated] = useState(false);

    const loadActivityDetails = (activityId) => {
        setActivityId(activityId);
    }

    const modal = (title, onSuccess) => {
        openModal(AddActivityModal, {
            onClose: closeModal,
            title: title,
            onSuccess: async () => {
                closeModal();
                setActivityDataUpdated(!activityDataUpdated);
            },
        })
    }

    return (
        <AppLayout>
            <h1 className="text-2xl pb-3 text-white">Activity</h1>

            <div className="grid grid-cols-3 gap-4">
                <div className="flex flex-col gap-3">
                    <Activities
                        status="SUBMITTED"
                        title="Scheduled Scripts"
                        loadActivityHandler={loadActivityDetails}
                        openModal={() => modal("Create new")}
                        activityDataUpdated={activityDataUpdated}
                    />
                    <Activities
                        status="RUNNING"
                        title="Currently Running"
                        loadActivityHandler={loadActivityDetails}
                    />
                    <Activities
                        status="COMPLETED"
                        title="Run History"
                        loadActivityHandler={loadActivityDetails}
                    />
                </div>
                {activityId ? (
                    <div className="col-span-2 col-start-2">
                      <ActivityDetails activityId={activityId}/>
                    </div>
                  ) : (
                    <div className="col-span-2 col-start-2">
                      <ActivityDetailsNoData />
                    </div>
                  )
                }
            </div>
        </AppLayout>
    )
}

export default ActivityPage;

Activities.js 组件

import React, {useState, useEffect} from "react";
import * as Icon from "react-bootstrap-icons";
import moment from "moment/moment";
import button from "bootstrap/js/src/button";
import {useActivities} from "../../api/activities";
import Loading from "../ui/Loading";

function Activities(props) {
    const {data, isFetching} = useActivities(props.status);
    
    const loadActivityDetails = (activityId) => {
        props.loadActivityHandler(activityId);
    }

    return (
      <div className="bg-widget rounded-md w-full px-3 pt-3 pb-6">
        <div className="flex justify-between items-center">
          <h1 className="text-2xl text-white">{props.title}</h1>
          {props.status === "SUBMITTED" && (
            <button onClick={props.openModal} className="btn btn-primary">Create new</button>
          )}
        </div>
        <div className="bg-dark border-1 border-greyDark rounded mt-3">
          {isFetching ? (
            <Loading />
          ) : (
            data && data.results && data.results.length > 0 ? (
              data.results.map((item, index) => (
                <div
                  key={index}
                  className="flex justify-between items-center px-3 py-2 border-b border-greyDark"
                >
                  <button className="text-left" onClick={() => loadActivityDetails(item.id)}>
                    <div className="font-bold">{item.id}{item.plan}</div>
                    <div>{moment(item.timestamp).format('DD-MMM-YYYY HH:mm°')}</div>
                  </button>
                  <button className="flex gap-1 items-center bg-grey hover:bg-greyDark rounded py-2 px-2">
                    <Icon.List className="w-4 h-4 text-white" />
                    <Icon.CaretDownFill className="w-3 h-3 text-white" />
                  </button>
                </div>
              ))
            ) : (
              <p className="text-center py-3">No data</p>
            )
          )}
        </div>
      </div>
    )
}

export default Activities;

API 请求代码

export const useActivityDetails = (activityId) => useQuery(
    {
        queryKey: [ACTIVITIES_DETAILS_QUERY_NAME, activityId],
        queryFn: () => api.get(`/activities/${activityId}`)
            .then((resp) => resp.data),
    }
)

解决方案

从代码里的useQuery可以看出你用的是React Query,实现无刷新更新不需要违反Hook规则,利用React Query的内置机制即可:

方法1:调用refetch方法手动刷新

首先确保useActivities返回refetch函数(基于useQuery的默认返回):

// 假设useActivities的实现
export const useActivities = (status) => useQuery({
  queryKey: ['activities', status], // 将status加入queryKey,区分不同状态的查询缓存
  queryFn: () => api.get(`/activities?status=${status}`).then(res => res.data)
})

在Activities组件中监听activityDataUpdated状态,触发refetch:

function Activities(props) {
    const {data, isFetching, refetch} = useActivities(props.status);
    
    useEffect(() => {
        if (props.activityDataUpdated) {
            refetch();
            // 通知父组件重置状态,避免重复触发
            props.resetActivityDataUpdated?.();
        }
    }, [props.activityDataUpdated, refetch])

    // 其余代码不变
}

在ActivityPage中添加重置状态的方法并传递给子组件:

function ActivityPage(props) {
    const [activityDataUpdated, setActivityDataUpdated] = useState(false);

    const resetActivityDataUpdated = () => {
        setActivityDataUpdated(false);
    }

    // 其余代码不变

    return (
        // ...
        <Activities
            status="SUBMITTED"
            title="Scheduled Scripts"
            loadActivityHandler={loadActivityDetails}
            openModal={() => modal("Create new")}
            activityDataUpdated={activityDataUpdated}
            resetActivityDataUpdated={resetActivityDataUpdated}
        />
        // ...
    )
}

方法2:使查询缓存失效(推荐)

在模态框提交成功后,直接调用React Query的invalidateQueries让相关缓存失效,自动触发重新获取:

首先在ActivityPage中引入useQueryClient:

import { useQueryClient } from '@tanstack/react-query';

function ActivityPage(props) {
    const queryClient = useQueryClient();
    // 其余状态定义不变

    const modal = (title, onSuccess) => {
        openModal(AddActivityModal, {
            onClose: closeModal,
            title: title,
            onSuccess: async () => {
                closeModal();
                // 使所有包含'activities'的查询缓存失效,自动重新获取
                await queryClient.invalidateQueries({ queryKey: ['activities'] });
                // 若只需刷新SUBMITTED状态的列表,可精准定位:
                // await queryClient.invalidateQueries({ queryKey: ['activities', 'SUBMITTED'] });
            },
        })
    }

    // 其余代码不变
}

这种方法不需要额外的activityDataUpdated状态,是React Query推荐的更新方式,能自动维护缓存一致性。

为什么之前的写法不成立?

你尝试在useEffect回调中调用useActivities,违反了React Hook的核心规则:Hook只能在组件顶层调用,不能在循环、条件判断或嵌套函数中执行。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:44:53