React出现useEffect/useState条件调用报错,useQuery设备数据状态赋值问题
问题原因分析
React Hook有强制调用规则:所有Hook必须在组件的最顶层调用,不能出现在条件判断、循环、嵌套函数、提前return的代码之后,保证每次组件渲染时Hook的调用顺序完全一致。你的代码违反了该规则,具体问题如下:
- 初始代码里
useEffect写在了loading、error两个提前return逻辑之后,一旦触发loading或者error状态,useEffect就不会执行,属于典型的条件调用Hook,因此抛出对应报错。 - 表单变更回调
HandleChange内部嵌套了useEffect,Hook不能在普通函数/事件回调中调用,只能在组件顶层或自定义Hook中调用,属于违规用法。 - 你修改后的拆分组件方案,将
deviceObject定义为全局变量、手动调用组件函数,完全不符合React组件渲染逻辑,不仅没有解决原有问题,还会引入数据不同步、内存泄漏等新问题。 - 原有
useEffect依赖数组为空,就算调整了调用位置,也只会在组件第一次挂载时执行,若dataRowId变化、查询结果更新,state不会同步更新,不符合业务需求。
解决方案
以下是修改后的完整可运行代码,完全符合你的业务需求,且无多余重渲染:
export default function DeviceModal(props) { const dataRowId = props.dataRowId; const classes = useStyles(); // 初始化state直接用空设备结构,避免初始值类型不匹配 const [row, setRow] = useState({ id : 0, deviceId : 0, deviceClass :{name : '',}, serialNumber: 0, }); const device = useQuery(getDevice_query, {variables: {id: dataRowId}}); // 所有Hook放在最顶部,条件return之前,符合调用规则 useEffect(() => { // 仅当查询完成、无错误、有返回数据时才更新state,保证setRow仅在必要时调用 if (!device.loading && !device.error && device.data) { const targetDevice = device.data.getDevice ?? { id : 0, deviceId : 0, deviceClass :{name : '',}, serialNumber: 0, }; setRow(targetDevice); } // 依赖项仅和查询状态相关,查询结果变化时才会重新执行 }, [device.loading, device.error, device.data]); // 表单变更逻辑直接写在回调中,不需要套useEffect const HandleChange = e => { // 用函数式更新保证拿到最新的state值,避免闭包问题 setRow(prev => ({...prev, [e.target.name]: e.target.value })); }; const handleSave = (value) => { // 自行补充提交保存逻辑 }; // 条件return放在所有Hook之后 if (device.loading) return <DataLoader/>; if (device.error) return <p style={{color: 'white'}}>{("GraphQL Error " + device.error)}</p>; return ( <div> <Modal ...> <div className={classes.paper}> <Grid container direction="row" justify="center" alignItems="center"> <Grid item xs={4}> <TextField id="deviceId" name="deviceId" // 改用受控组件的value属性,保证输入和state完全同步 value={row.deviceId} onChange={HandleChange} /> </Grid> {/* 其余30个TextField按相同规则配置value和onChange即可 */} .... </Grid> </div> </Modal> </div> ) };
内容的提问来源于stack exchange,提问作者Neal Rogers
相关产品推荐
相关产品推荐

