Search code examples
reactjstypescriptmaterial-uiformikyup

Problems setting the values ​of a form using Material UI, Formik and Typescript


I'm trying to set the 'role' and 'active' values ​​on a form.

The first problem is that the original values ​​of the selectors are not set, which are fetched in the ManageUserDialog Props, so when starting the component the selectors are shown empty.

The second problem is that selecting one of the selector options doesn't set its value.

I attach images of the component, the code and the error delivered by the console.

enter image description here

enter image description here

import {
    Button,
    Dialog,
    DialogActions,
    DialogContent,
    DialogTitle,
    useMediaQuery,
    useTheme,
    Grid,
    TextField,
    MenuItem
} from '@mui/material';
import { Formik } from "formik";
import * as yup from 'yup';
import * as UsersApi from '../network/UsersApi';
import { useGlobalState } from '../context/hookStateProvider';


interface ManageUserFormInterface {
    role: number;
    active : boolean;
}

const ManageUserSchema = yup.object().shape({
    role: yup.number().required("Required"),
    active: yup.boolean().required("Required"),
});

interface Props {
    userId: string;
    userName: string;
    active: boolean;
    role: number;
    isDialogOpen: boolean;
    handleCloseDialog: () => void;
}

const roleItems = [
    {label: 'User', value: 0},
    {label: 'Manager', value: 1},
    {label: 'Admin', value: 2}
];

const activeItems = [
    {label: 'Yes', value: true},
    {label: 'No', value: false}
];

const ManageUserDialog = ({userId, userName, active, role, isDialogOpen, handleCloseDialog}: Props) => {
    const theme = useTheme();
    const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
    const state = useGlobalState();

    const initialValues: ManageUserFormInterface = {
        role: role,
        active: active,
    }

    const handleOkDialog = async (values: ManageUserFormInterface) => {
        state.setLoading(true);

        const { role, active } = values;
        try {
            await UsersApi.manageUser({userId, role, active});
        } catch (error) {
            console.log(error);
        } finally {
            state.setLoading(false);
            handleCloseDialog();
        }
    }
    
    return (
        <Dialog
            fullScreen={fullScreen}
            open={isDialogOpen}
            onClose={handleCloseDialog}
            aria-labelledby="responsive-dialog-title"
            sx={{
                "& .MuiDialog-container": {
                    "& .MuiPaper-root": {
                        minWidth: "300px",
                    },
                },
            }}
        >
            <DialogTitle id="responsive-dialog-title">
                {`Manage ${userName}`}
            </DialogTitle>
            <DialogContent>
            <Formik
                    onSubmit={handleOkDialog}
                    initialValues={initialValues}
                    validationSchema={ManageUserSchema}
                >
                    {({ values, errors, touched, handleBlur, handleChange, handleSubmit}) => (
                        <form onSubmit={handleSubmit} id="ManageUserForm">
                            <Grid container spacing={2} sx={{ mt: 3 }}>
                                <Grid item xs={12}>
                                    <TextField
                                        fullWidth
                                        variant="filled"
                                        select
                                        label="Role"
                                        onBlur={handleBlur}
                                        onChange={handleChange}
                                        value={values.role}
                                        id="role"
                                        name="role"
                                        error={!!touched.role && !!errors.role}
                                        helperText={touched.role && errors.role}
                                        defaultValue={role}
                                    >
                                        <MenuItem>
                                            {roleItems.map((role, index) => (
                                                <MenuItem key={index} value={role.value}>
                                                    {role.label}
                                                </MenuItem>
                                            ))}
                                        </MenuItem>
                                    </TextField>
                                </Grid>
                                <Grid item xs={12}>
                                    <TextField
                                        fullWidth
                                        variant="filled"
                                        select
                                        label="Active"
                                        onBlur={handleBlur}
                                        onChange={handleChange}
                                        value={values.active}
                                        id="active"
                                        name="active"
                                        error={!!touched.active && !!errors.active}
                                        helperText={touched.active && errors.active}
                                        defaultValue={active}
                                    >
                                        <MenuItem>
                                            {activeItems.map((active, index) => (
                                                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                                <MenuItem key={index} value={active.value as any}>
                                                    {active.label}
                                                </MenuItem>
                                            ))}
                                        </MenuItem>
                                    </TextField>
                                </Grid>
                            </Grid>
                        </form>
                    )}
                </Formik>
            </DialogContent>
            <DialogActions>
            <Button onClick={handleCloseDialog}>
                Cancel
            </Button>
            <Button type='submit' form="ManageUserForm">
                Confirm
            </Button>
            </DialogActions>
        </Dialog>
    )
}

export default ManageUserDialog;

I tried changing TextField for Select but it happens the same.


Solution

  • I solved it, the problem was that I used MenuItem inside MenuItem. I only left the one inside the map in each TextField.

    import {
        Button,
        Dialog,
        DialogActions,
        DialogContent,
        DialogTitle,
        useMediaQuery,
        useTheme,
        Grid,
        TextField,
        MenuItem
    } from '@mui/material';
    import { Formik } from "formik";
    import * as yup from 'yup';
    import * as UsersApi from '../network/UsersApi';
    import { useGlobalState } from '../context/hookStateProvider';
    
    
    interface ManageUserFormInterface {
        role: number;
        active : boolean;
    }
    
    const ManageUserSchema = yup.object().shape({
        role: yup.number().required("Required"),
        active: yup.boolean().required("Required"),
    });
    
    interface Props {
        userId: string;
        userName: string;
        active: boolean;
        role: number;
        isDialogOpen: boolean;
        handleCloseDialog: () => void;
    }
    
    const roleItems = [
        {label: 'User', value: 0},
        {label: 'Manager', value: 1},
        {label: 'Admin', value: 2}
    ];
    
    const activeItems = [
        {label: 'Yes', value: true},
        {label: 'No', value: false}
    ];
    
    const ManageUserDialog = ({userId, userName, active, role, isDialogOpen, handleCloseDialog}: Props) => {
        const theme = useTheme();
        const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
        const state = useGlobalState();
    
        const initialValues: ManageUserFormInterface = {
            role: role,
            active: active,
        }
    
        const handleOkDialog = async (values: ManageUserFormInterface) => {
            state.setLoading(true);
    
            const { role, active } = values;
            try {
                await UsersApi.manageUser({userId, role, active});
            } catch (error) {
                console.log(error);
            } finally {
                state.setLoading(false);
                handleCloseDialog();
            }
        }
        
        return (
            <Dialog
                fullScreen={fullScreen}
                open={isDialogOpen}
                onClose={handleCloseDialog}
                aria-labelledby="responsive-dialog-title"
                sx={{
                    "& .MuiDialog-container": {
                        "& .MuiPaper-root": {
                            minWidth: "300px",
                        },
                    },
                }}
            >
                <DialogTitle id="responsive-dialog-title">
                    {`Manage ${userName}`}
                </DialogTitle>
                <DialogContent>
                <Formik
                        onSubmit={handleOkDialog}
                        initialValues={initialValues}
                        validationSchema={ManageUserSchema}
                    >
                        {({ values, errors, touched, handleBlur, handleChange, handleSubmit}) => (
                            <form onSubmit={handleSubmit} id="ManageUserForm">
                                <Grid container spacing={2} sx={{ mt: 3 }}>
                                    <Grid item xs={12}>
                                        <TextField
                                            fullWidth
                                            variant="filled"
                                            select
                                            label="Role"
                                            onBlur={handleBlur}
                                            onChange={handleChange}
                                            value={values.role}
                                            id="role"
                                            name="role"
                                            error={!!touched.role && !!errors.role}
                                            helperText={touched.role && errors.role}
                                        >
                                            {/* Here Changes */}
                                            {roleItems.map((r, index) => (
                                                <MenuItem key={index} value={r.value}>
                                                    {r.label}
                                                </MenuItem>
                                            ))}
                                        </TextField>
                                    </Grid>
                                    <Grid item xs={12}>
                                        <TextField
                                            fullWidth
                                            variant="filled"
                                            select
                                            label="Active"
                                            onBlur={handleBlur}
                                            onChange={handleChange}
                                            value={values.active}
                                            id="active"
                                            name="active"
                                            error={!!touched.active && !!errors.active}
                                            helperText={touched.active && errors.active}
                                        >
                                            {/* Here Changes */}
                                            {activeItems.map((active, index) => (
                                                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                                <MenuItem key={index} value={active.value as any}>
                                                    {active.label}
                                                </MenuItem>
                                            ))}
                                        </TextField>
                                    </Grid>
                                </Grid>
                            </form>
                        )}
                    </Formik>
                </DialogContent>
                <DialogActions>
                <Button onClick={handleCloseDialog}>
                    Cancel
                </Button>
                <Button type='submit' form="ManageUserForm">
                    Confirm
                </Button>
                </DialogActions>
            </Dialog>
        )
    }
    
    export default ManageUserDialog;