React父组件接收子组件回调设置State不生效如何解决
React回调更新父组件state后取值异常问题
问题现象
我尝试通过回调函数在父组件中设置state,该回调会向下传递给渲染Material UI DataTable的子组件,回调响应onClick事件并传递相关参数,目前存在的问题是:使用回调传入的参数值设置state无法正常生效。
我的预期逻辑是:用户点击子组件内的按钮时,会触发回调函数,并传入设置state所需的对应值。
问题复现代码
父组件代码
export default function ViewJobs() { const [type, setType] = useState(''); const [params, setParams] = useState({}); const callback = ({ cellValues, componentType, path }) => { setType(componentType); setParams(cellValues) // Sets the params with an object. console.log(cellValues) // Displays the data I need in the console history.push(path); }; console.log(params) // Displays undefine in the console. return( <React.Fragment> <TabPanel value={value} index={0} dir={theme.direction} > <DataTable jobs={job} title='All' parentCallback={callback} /> </TabPanel> </React.Fragment> ); }
子组件代码
import React, { useEffect } from 'react'; export default function DataTable(props) { const { jobs, parentCallback } = props; const rows = jobs.payload; const handleDiaryClick = (event, cellValues) => { const params = { cellValues, componentType: 'diary', path: "/view/jobs/diary" }; parentCallback(params); }; const renderDiaryElement = params => { return ( <Button variant="contained" color="primary" style={{ backgroundColor: "#000000" }} onClick={(event) => { handleDiaryClick(event, params); }} > <MenuBookIcon /> </Button> ); } return ( <div className={classes.root} style={{ height: 400, width: '100%' }} > <DataGrid rows={rows} columns={columns} pageSize={5} //checkboxSelection disableSelectionOnClick /> </div> ); }
调试表现
我已经将状态提升至父组件,按照逻辑上述代码应当可以正常运行。调试时发现回调函数内的console.log(cellValues)可以正常打印所需数据,但组件顶层的console.log(params)却显示undefined。我尝试独立复现该问题但未能成功。
问题原因
- React的state更新是异步批处理机制:调用
setParams后state不会立刻同步更新,当前函数作用域内无法立刻拿到更新后的值,需要等组件重渲染后才能获取到最新state。 - 你在调用state更新方法后立刻执行
history.push(path)跳转路由,如果目标路由不渲染当前ViewJobs组件,组件会直接卸载,来不及完成state更新和重渲染流程,state更新操作会被丢弃;如果路由配置导致跳转后组件重新挂载,state会被重置为初始值,就会出现取值异常的问题。
解决方法
根据业务场景二选一即可:
方案1:路由跳转直接携带参数(推荐)
如果目的是跳转页面时把参数传递给目标路由页,不需要把参数存在当前父组件的state里,直接在路由跳转时通过路由state携带参数即可,完全避开state异步更新的问题:
// 以react-router v6为例,v5版本可直接在history.push的第二个参数传state import { useNavigate } from 'react-router-dom'; export default function ViewJobs() { const navigate = useNavigate(); const [type, setType] = useState(''); const [params, setParams] = useState({}); const callback = ({ cellValues, componentType, path }) => { setType(componentType); setParams(cellValues); // 跳转时直接携带参数,目标页可直接读取 navigate(path, { state: { componentType, cellValues } }); }; return( <React.Fragment> <TabPanel value={value} index={0} dir={theme.direction} > <DataTable jobs={job} title='All' parentCallback={callback} /> </TabPanel> </React.Fragment> ); }
目标路由页可以通过useLocation拿到携带的参数:
import { useLocation } from 'react-router-dom'; const DiaryPage = () => { const location = useLocation(); const { componentType, cellValues } = location.state || {}; // 直接使用参数即可 }
方案2:监听state更新完成后再跳转
如果确实需要先把参数存在父组件state中,再执行跳转,可以通过useEffect监听相关state变化,确认state更新完成后再触发路由跳转:
export default function ViewJobs() { const [type, setType] = useState(''); const [params, setParams] = useState({}); const [targetPath, setTargetPath] = useState(''); const callback = ({ cellValues, componentType, path }) => { setType(componentType); setParams(cellValues); setTargetPath(path); }; // 监听state和目标路径,都准备好后再跳转 useEffect(() => { if (targetPath && Object.keys(params).length) { history.push(targetPath); // 重置跳转路径避免重复触发 setTargetPath(''); } }, [params, targetPath, history]); return( <React.Fragment> <TabPanel value={value} index={0} dir={theme.direction} > <DataTable jobs={job} title='All' parentCallback={callback} /> </TabPanel> </React.Fragment> ); }
内容的提问来源于stack exchange,提问作者Stephen Melben Corral
相关产品推荐
相关产品推荐

