You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 20:13:17