I have a component that uses an object as a value.
I would like to use this component with react-hook-form
The problem is that react-hook-form
thinks that my object is a nested form control
This is just an example.
Range date picker is a common use case for such a behaviour
The value that the component accepts:
type ComponentValue = {
a: string;
b: string;
}
The component:
const Component = ({ value, onChange }: { value: ComponentValue, onChange: (value: ComponentValue) => void }) => {
return (
<input value={value.a} onChange={e => onChange({ a: e.target.value, b: (Math.random() * 100 | 0).toString() }) } />
)
}
Form value
type FormValues = {
someField: ComponentValue;
// other fields
};
Default values:
const defaultValues = {
someField: {
a: 'Default a',
b: 'Default b'
},
// other fields
};
useForm
usage:
const {
register,
handleSubmit,
formState: { errors },
control,
} = useForm<FormValues>({
defaultValues,
});
Hovering over (or trying to use) errors
reveals the type:
const errors: {
someField?: {
a?: FieldError | undefined;
b?: FieldError | undefined;
} | undefined;
// other fields
}
But I would it to be:
const errors: {
someField?: FieldError | undefined;
// other fields
}
Can I somehow force react-hook-form
to treat my object as a value instead of a nested form field?
If you want to combine 2 inputs in a same component, you can try the approach with a controller.
export default function App() {
const { control, handleSubmit, watch } = useForm({
defaultValues: {
travel: {
from: "paris",
to: "london"
}
}
});
const onSubmit = (data) => {
console.log("dans onSubmit", JSON.stringify(data, null, 2));
};
return (
<>
<h1>Travel</h1>
<form
onSubmit={handleSubmit(onSubmit)}
style={{ display: "flex", flexDirection: "column", maxWidth: 600 }}
noValidate
>
<TravelPicker control={control} />
<input type="submit" />
<pre>{JSON.stringify(watch(), null, 2)}</pre>
</form>
</>
);
The component have a control props, to be able to register the 2 inputs:
const TravelPicker = ({ control }) => {
return (
<>
<label>
From
<Controller
name="travel.from"
control={control}
render={({ field: { onChange, value, name } }) => (
<input name={name} value={value} onChange={(e) => onChange(e)} />
)}
/>
</label>
<label>
To
<Controller
name="travel.to"
control={control}
render={({ field: { onChange, value, name } }) => (
<input name={name} value={value} onChange={(e) => onChange(e)} />
)}
/>
</label>
</>
);
};
The errors object has then the same structure as the form inputs.
See this sandbox for a functional exemple.
The createor of RHF seems to indicate here that you can also have only one component for 2 inputs.
You can use setValue
and setError
like below:
const {setValue, setError} = useForm();
// ... some where in your form
<input
type = "text"
name = "from"
onChange = {
(event) => {
const value = event.target.value;
if(value === ""){
setError("from", "This field is required");
}else{
setValue(`from[0]`, {
"otherProperty" : "otherValue",
"from_value": value, // this is your input value
});
}
}
}
/>
Probably you would be iterating through an array. change static 0 with your index in setValue
If not you may use object syntax like below:
//....
setValue(`from.your_property_name`, {
"otherProperty" : "otherValue",
"from_value": value, // this is your input value
});
// ....
This won't cause re-render of component.
More info in doc
NOTE: Please be respectful when commenting.
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.