I am trying to use useFieldArray for dynamic Select field of Material UI and react-hook-form. It works fine with TextField, but when used with Select it doesn't work..
What doesn't work..
defaultValue
doesn't show in Select, but shows in TextFieldHere is the code of Select component
import * as React from "react";
import {
FormControl,
FormHelperText,
InputLabel,
MenuItem,
OutlinedInput,
Select,
SelectChangeEvent
} from "@mui/material";
import { useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
import Placeholder from "./Placeholder";
export interface IOptionTypes {
id: string;
label: string;
value: string;
}
interface IFormElementTypes {
name: string;
label: string;
required?: boolean;
defaultValue: string;
options: IOptionTypes[];
placeholder: string;
}
export default function MultiSelectField({
name,
label,
required,
defaultValue,
options,
placeholder
}: IFormElementTypes) {
const {
control,
register,
formState: { errors }
} = useFormContext();
// const defaultVal = control._defaultValues[name];
const [selectedVal, setSelectedVal] = useState<string[]>([]);
const handleChange = (event: SelectChangeEvent<typeof selectedVal>) => {
const {
target: { value }
} = event;
setSelectedVal(
// On autofill we get a stringified value.
typeof value === "string" ? value.split(",") : value
);
};
const labelText = `${label}${required ? "*" : ""}`;
const labelId = `multi-select-${name}`;
return (
<Controller
name={name}
defaultValue={defaultValue}
control={control}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel
shrink
sx={{ backgroundColor: "white", padding: "0px 2px" }}
error={!!errors[name]}
id={labelId}
>
{labelText}
</InputLabel>
<Select
{...field}
label={labelText}
labelId={labelId}
value={selectedVal}
displayEmpty
multiple
{...register(`${name}` as const)}
variant="outlined"
fullWidth
error={!!errors[name]}
input={
<OutlinedInput
id={`multi-select-${label}`}
label={`${label}${required ? "*" : ""}`}
/>
}
renderValue={
selectedVal.length > 0
? undefined
: () => <Placeholder>{placeholder}</Placeholder>
}
onChange={handleChange}
>
{options.map((option) => (
<MenuItem key={option.id} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText>{String(errors[name]?.message ?? "")}</FormHelperText>
</FormControl>
)}
/>
);
}
and How it is been called finally
import * as React from "react";
import {
useForm,
useFieldArray,
FormProvider,
SubmitHandler
} from "react-hook-form";
import { Button, Grid, Stack, Box } from "@mui/material";
import MultiSelectField, { IOptionTypes } from "./MultiSelectField";
import TextFieldElement from "./TextFieldElement";
type FormValues = {
items: { name: string; age: string }[];
};
const defaultValues = {
items: [{ name: "john", age: "10" }]
};
const names: IOptionTypes[] = [
{ id: "1", label: "John", value: "john" },
{ id: "2", label: "Smith", value: "smith" },
{ id: "3", label: "Julia", value: "julia" },
{ id: "4", label: "David", value: "david" }
];
export default function App() {
const methods = useForm<FormValues>({
defaultValues,
mode: "all"
});
const { control, formState, setError } = methods;
const { fields, append, remove } = useFieldArray({ name: "items", control });
const formSubmitHandler: SubmitHandler<FormValues> = async (
data: FormValues
) => {
console.log("Form values ", data);
};
return (
<Grid container>
<Grid item xs={12}>
<Stack direction="row" spacing={2} my={2}>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
onClick={() => {
append({ name: "", age: "" });
}}
>
Add Field
</Button>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
onClick={() => {
remove(fields.length - 1);
}}
>
Remove Field
</Button>
</Stack>
</Grid>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(formSubmitHandler)}>
<Grid item xs={12}>
<Stack spacing={2}>
{fields.map((field, index) => (
<Stack direction="row" spacing={2} key={field.id}>
<MultiSelectField
name={`items.${index}.name`}
label="Choose Name"
placeholder="Name"
defaultValue=""
options={names}
/>
<TextFieldElement
name={`items.${index}.age`}
label="Input Age"
placeholder="Age"
/>
</Stack>
))}
</Stack>
</Grid>
<Grid container my={2}>
<Grid item>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
>
Save Changes
</Button>
</Grid>
</Grid>
</form>
</FormProvider>
</Grid>
);
}
As in the code above, I have set default values, which works for TextField but not for the SelectField
I wonder what is wrong in there..
Here is my Codesandbox, thanks for help.
You're going to want to make sure that you set the value
prop on your Select
inside of MultiSelectField
to selectedVal
:
<Select
{...field}
label={labelText}
labelId={labelId}
value={selectedVal}
displayEmpty
multiple
{...register(`${name}` as const)}
variant="outlined"
fullWidth
error={!!errors[name]}
input={
<OutlinedInput
id={`multi-select-${label}`}
label={`${label}${required ? "*" : ""}`}
/>
Also remove the defaultValue={defaultValue}
from the Controller
in MultiSelectfield
so the Controller
looks like this:
<Controller
name={name}
control={control}
render={({ field }) => (
Since you are using controlled components, we need to make sure we set the default value of selectedVal
to the default value passed in as a prop:
const [selectedVal, setSelectedVal] = useState<string[]>(defaultValue);
selectedVal
is supposed to be a string[]
not a string
, so we should change the type of defaultValue
to a string[]
:
interface IFormElementTypes {
name: string;
label: string;
// eslint-disable-next-line react/require-default-props
required?: boolean;
defaultValue: string[];
options: IOptionTypes[];
placeholder: string;
}
Finally, back in App.tsx
we should pass an array to MultiSelectField
as the defaultValue
prop. Here's what it would look like if we wanted "John" to be selected by default:
<MultiSelectField
name={`items.${index}.name`}
label="Choose Name"
placeholder="Name"
defaultValue={["john"]}
options={names}
/>
Or, if we wanted to have the default be "John" and "Smith" then we could set the default like this:
defaultValue={["john", "smith"]}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.