React Hook Form动态权限表单:受控状态切换报错如何解决?
解决React Hook Form动态权限表单的受控/非受控状态错误
你遇到的错误是因为动态加载权限数据时,React Hook Form的表单字段初始值未正确初始化,导致Checkbox组件从非受控状态切换为受控状态。当value初始为undefined时,组件是非受控的,一旦用户修改后值变为布尔值,就会触发这个警告。
核心解决方案
- 动态初始化表单默认值:在权限数据加载完成后,将其转换为React Hook Form需要的结构,通过
reset方法设置默认值。 - 确保Checkbox的checked值始终为布尔类型:使用空值合并运算符(
??)或双重否定(!!)避免undefined导致的非受控状态。 - 统一用Controller管理所有表单控件:包括顶部的"manage"复选框,确保所有状态都由React Hook Form管控。
修改后的代码示例
完整表单组件(FormPermissions)
import { Box } from '@mui/material' import React, { useEffect, useState } from 'react' import { FormProps } from './FormPassword' import PermissionBox from './PermissionBox' import { getPermisos } from '@/services/usuarios' import { useForm } from 'react-hook-form' const FormPermissions = (props: FormProps) => { const { idUser } = props; const [permisos, setPermisos] = useState([]); const methods = useForm(); const { control, handleSubmit, reset } = methods; useEffect(() => { getPermisos(idUser).then(res => { const permissionsData = res.data; setPermisos(permissionsData); // 将权限数据转换为React Hook Form需要的结构 const defaultValues = permissionsData.reduce((acc, permiso) => { permiso.actions.forEach(action => { acc[`permisos.${idUser}.${permiso.subject}.${action.id}`] = action.checked; }); return acc; }, {}); // 动态设置表单默认值 reset(defaultValues); }) }, [idUser, reset]); const changePermissions = (data: any) => { ///TODO change permissions console.log(data); } return ( <Box component='form' id={props.id} sx={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-around', gap: '1rem', p: '10px', alignItems: 'center' }} onSubmit={handleSubmit(changePermissions)} > {permisos.map((permiso: any, idx: number) => ( <PermissionBox key={idx} permiso={permiso} idUser={idUser} control={control} /> ))} </Box> ) } export default FormPermissions
单个主题权限组件(PermissionBox)
const PermissionBox = (props: PermissionBoxProps) => { const { permiso, idUser, control } = props; const [manage, ...actions] = permiso.actions.toReversed(); return ( <Grid item xs={2} sx={{ display: 'flex', flexDirection: 'column', border: 1, p: 1, }} > <Typography variant='h6'> {permiso.subject} </Typography> <Divider sx={{ margin: '0px -10px 0px -10px', borderBottomWidth: 2 }} /> {manage && ( <FormGroup> <Controller name={`permisos.${idUser}.${permiso.subject}.${manage.id}`} control={control} render={({ field: { onChange, value } }) => ( <FormControlLabel label={traduceAction(manage.action)} control={ <Checkbox checked={value ?? false} // 确保值为布尔类型 onChange={onChange} indeterminate={isIndeterminated(actions, manage)} /> } /> )} /> </FormGroup> )} {actions.map((action, index) => ( <FormGroup key={index} sx={{ ...(action.action !== 'manage' && { ml: 2 }), }}> <Controller name={`permisos.${idUser}.${permiso.subject}.${action.id}`} control={control} render={({ field: { onChange, value } }) => ( <FormControlLabel label={traduceAction(action.action)} control={ <Checkbox checked={value ?? false} // 避免undefined onChange={onChange} indeterminate={isIndeterminated(actions, manage)} /> } /> )} /> </FormGroup> ))} </Grid> ) };
关键修改点说明
- 在
FormPermissions中,权限数据加载完成后,通过reduce将数据转换为表单字段对应的键值对,再调用reset设置默认值,确保所有字段初始值都被正确初始化。 - 在
PermissionBox的所有Checkbox中,使用value ?? false确保checked属性始终是布尔值,避免undefined导致的非受控状态。 - 将顶部的"manage"复选框也纳入
Controller管理,统一表单状态的管控逻辑。
内容的提问来源于stack exchange,提问作者Miguel V
相关产品推荐
相关产品推荐

