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
相关产品推荐
相关产品推荐

