父组件Audit更新state后,子组件CommonDataTable的useEffect未触发
我有两个独立组件Audit和Reports,二者均渲染CommonDataTable子组件。CommonDataTable中依赖props.data的useEffect钩子在Reports组件中可正常执行,但在Audit组件中,即便父组件将自身state作为props传入,该钩子也不会触发。
Audit组件
function Audit(props: any) { const [tableData, setTableData] = React.useState([]); const [totalRecords, setTotalRecords] = React.useState(0); //fetch data and set to state const { refetch } = useQuery(GET_AUDIT_DATA, { variables: { inputAuditData: { perPage: perPage, page: currentPage, filterText: "", sortBy: "", sortOrder: "" } }, onCompleted: (data: any) => { console.log("refetch compmleted") setTableData(data.records) setTotalRecords(data.totalCount) } }); // refetch for data dictionary on data table parameter change const refreshData = async ( perPage: number, currentPage: number, filterText:"", sortBy: string, sortOrder: string) => { await refetch({ inputAuditData: { perPage: perPage, page: currentPage, filterText: filterText, sortBy: sortBy, sortOrder: sortOrder } }) } return ( <> {console.log("Audit tableData", tableData)} <CommonDataTable data={tableData} totalNoRows={totalRecords} refreshData={refreshData} /> </> ) }
Reports组件
function Reports(props: any) { const [tableData, setTableData] = React.useState([]); const [totalRecords, setTotalRecords] = React.useState(0); // fetch report data and set the redux store const { refetch } = useQuery(GET_REPORTS_DATA, { variables: { inputDataDictionary: { modelID: parseInt(modelId), variables: "", perPage: perPage, page: currentPage, filterText: "", sortBy: "", sortOrder: "" }}, onCompleted: (data: any) => { console.log("refetch compmleted") props.initModelData(data) //set data to redux store setTotalRecords(data.totalCount) } }); // refetch for reports data on data table parameter change const refreshData = async (perPage: number, currentPage: number, filterText: string, sortBy: string, sortOrder: string ) => { sortBy = sortBy === 'localizedType' ? 'type' : sortBy await refetch({ inputDataDictionary: { modelID: parseInt(modelId), variables: "", perPage: perPage, page: currentPage, filterText: "", sortBy: sortBy, sortOrder: sortOrder } }) } // react hook watches change in redux store and sets state using setTableData() React.useEffect(() => { setTableData(props.modelData) }, [props.modelData]); return ( <> {console.log("Reports tableData", tableData)} <CommonDataTable data={tableData} totalNoRows={totalRecords} refreshData={refreshData} /> </> ) }
CommonDataTable组件
export default function CommonDataTable(props: any) { const [tableData, setTableData] = React.useState([]); // should execute to update table data when the data is being refreshed React.useEffect(() => { console.log("CD setTableData in useEffect ******* ") setTableData(props.data) }, [props.data]); return ( {console.log("CD tableData", tableData)} <DataTable data={tableData} /> ) }
如你所见,Audit组件获取数据后直接通过setTableData存入本地state;而Reports组件获取数据后先存入Redux store,再通过监听props.modelData的useEffect将数据存入本地state。两个组件均将tableData作为props传入CommonDataTable组件。
请问为何父组件Audit中setTableData更新state后,子组件CommonDataTable的useEffect钩子未执行?
问题原因及解决方案
1. 数组引用未变化,React浅比较未检测到更新
Audit组件中tableData初始值是[],如果data.records也是空数组(比如首次查询无数据),setTableData(data.records)设置的是同一个引用的数组。React对state的更新检测是浅比较,引用相同会认为state没有变化,父组件不会重新渲染,子组件的props.data也不会更新,因此useEffect不会触发。
解决方法:设置state时创建新的数组引用,比如:
setTableData([...data.records])
2. useQuery缓存导致数据未更新
如果useQuery的缓存中已有相同变量的查询结果,onCompleted可能不会触发,或者返回的data.records和之前的state引用一致。可以调整useQuery的配置,比如:
const { refetch } = useQuery(GET_AUDIT_DATA, { variables: { ... }, onCompleted: (data) => { ... }, cacheTime: 0, // 禁用缓存 refetchOnMount: true // 组件挂载时重新查询 })
3. CommonDataTable冗余本地state导致同步问题
CommonDataTable内部维护的tableData本地state是冗余的,完全可以直接使用props.data,避免useEffect的执行时机问题。简化后的组件代码:
export default function CommonDataTable(props: any) { return ( <> {console.log("CD tableData", props.data)} <DataTable data={props.data} /> </> ) }
这样父组件state更新后,子组件会直接渲染最新数据,无需依赖内部的useEffect。
内容的提问来源于stack exchange,提问作者ilya_i

