It's been 3 months I learn ReactJS TypeScript. My question is about to use react-hook-form (v7) for editing a form. I want to use my custom component that I created and found how to do it by myself !
Here is a part of my form provider with react-hook-form
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import InputText from 'components/commons/form/InputText';
import { supabase } from 'configs/supabase';
const EditEducation: React.FC = () => {
const { educationId } = useParams();
const [education, setEducation] = useState<education>();
const getEducation = async (educationId: string | undefined) => {
try {
const { data, error } = await supabase
.from('tables1')
.select('data1, data2')
.eq('id', educationId)
.single();
if (error) {
seterror(error.message);
}
if (data) {
return data;
}
} catch (error: any) {
alert(error.message);
}
};
useEffect(() => {
getEducation(educationId).then((data) => {
setEducation(data);
});
// eslint-disable-next-line
}, [educationId]);
const methods = useForm();
const onSubmit = async (formData: any) => {
const updateData = {
data1 = formData.data1,
data2 = formData.data2
};
try {
setSaving(true);
const { error } = await supabase.from('educations').update(updateData);
if (error) {
seterror(error.message);
}
if (!error) {
navigate('/experiences/education');
}
setSaving(false);
} catch (error: any) {
seterror(error.message);
}
};
return (
...
<FormProvider {...methods}>
<form className="p-4" onSubmit={methods.handleSubmit(onSubmit)}>
<InputText
id="data1"
label="Data1"
placeholder="Ex: data1"
defaultValue={education?.data1}
options={{ required: 'This field is required' }}
/>
<Button type="submit">{saving ? 'Saving' : 'Save'}</Button>
</form>
</FormProvider>
...
)
};
Here is my custom component :
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
interface InputProps {
id: string;
label: string;
placeholder?: string;
defaultValue?: string;
}
const InputText: React.FC<InputProps> = ({
id,
label,
placeholder,
defaultValue,
options,
...rest
}: InputProps) => {
const {
register,
setValue,
formState: { errors }
} = useFormContext();
useEffect(() => {
if (defaultValue) setValue(id, defaultValue, { shouldDirty: true });
}, [defaultValue, setValue, id]);
return (
<div className="">
<label htmlFor={id} className="">
{label}
</label>
<input
type="text"
placeholder={placeholder}
className=""
id={id}
defaultValue={defaultValue}
{...register(id, options)}
{...rest}
/>
{errors[id] && (
<p className="">
<span className="">*</span> {errors[id]?.message}
</p>
)}
</div>
);
};
export default InputText;
As you can see, I had use a formContext because I want to deconstruct my code into smaller components.
Now I'm having some doubts if I correctly code, specialy when I use ut editing forms : if set my default value via "defaultValue" prop, I have to submit (error show) then clique inside the input to change the state in order to clean the error in the input component.
This is why I have add the useEffect hook to clean the input validation error and it's working. What do you think about this ? Is there a better way to manage it (I think Yup it's a cleaner way to set the validation schema) ?
Thanks in advance and sorry for my rusty English. Great day to all and hope my code will help people.
Use <FormProvider {...methods}> and it's working but I do not know if it's a good way to do it.
Edit : In reality, I have to double submit to get my data so I guess it's not the correct way, any sugestions ?
Edit2 : I have found a "solution" : if I have a defaultValue in my props, I do in my component :
useEffect(() => {
if (defaultValue) setValue(id, defaultValue, { shouldDirty: true });
}, [defaultValue, setValue, id]);
I do not think it is the better solution ...
CodePudding user response:
You should provide default values to useForm
, not to your component (so your InputText
doesn't need to know about defaultValue
or setValue
, it will have the correct value thanks to the register
method).
To initialize the form, you can do
useForm({ defaultValues: { data1: education?.data1 } });
If the data you use to provide default values is loading after the form is initialized, you can use the reset
method (see docs), which I personally put in a useEffect
to watch for data update:
const Component: React.FC = ({ defaultValues }) => {
const {
register,
handleSubmit,
reset,
} = useForm({ defaultValues });
useEffect(() => {
reset(defaultValues);
}, [defaultValues, reset]);
return ...
}
On another note, you should define getEducation
in the useEffect
that calls it, instead of in the component, so that the method isn't declared every time your component is rendered. Snippet:
useEffect(() => {
const getEducation = () => {
...
};
getEducation();
}, [educationId]);