I have a form for adding a cocktail recipe. I am using React-Hook-Form to build it. One of the sections is list of ingredients. For a start there should be only a single row. If the "Add ingredient" button is clicked, another row should show.
I can get it to add a row, but I cannot get it to re-render, when that happens. I am aware that I could probably do it with useState
, but I cannot figure out how to use that with React-Hook-Forms, if that’s even possible.
Here is how it looks in the initial render:
And after clicking the "Tilføj ingediens" (add ingredient), it should look like this:
However, this doesn’t happen. From using console.log
it appears that the ingredients array is in fact updated. It just doesn’t re-render the form. Does anyone know how to do that, without abusing React (like triggering a re-render by changing some hidden component, etc.)? I have tried looking into the watch
feature of React-Hook-Forms. It feels like a step in the right direction, but I cannot figure out how exactly it can be done.
Code
import FileInput from "../components/FileInput";
import Input from "../components/Input";
import Label from "../components/Label";
import TextArea from "../components/TextArea";
import { useForm, SubmitHandler } from "react-hook-form";
interface Cocktail {
name: string;
description: string;
imageUrl: string;
ingredients: Ingredient[];
}
interface Ingredient {
name: string;
quantity: string;
unit: string;
}
const CreateCocktail = () => {
const {register, getValues, handleSubmit} = useForm<Cocktail>({
defaultValues: {
name: '',
description: '',
imageUrl: '',
ingredients: [
{name: '', quantity: '', unit: ''}, {name: '', quantity: '', unit: ''}
]
}
});
const onAddIngredient = () => {
// This part should somehow trigger a re-render of the form.
console.log("Adding ingredient");
getValues().ingredients.push({name: '', quantity: '', unit: ''});
}
const onSubmit: SubmitHandler<Cocktail> = async data => {
try {
console.log("Submitting cocktail", data);
data.description = "Test description";
data.imageUrl = "Test image url";
const response = await fetch('/api/cocktails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
console.log('Cocktail created');
} else {
console.log('Error creating cocktail');
}
} catch (error) {
console.log(error);
}
};
return (
<>
<div>
<form onSubmit={handleSubmit(onSubmit)} >
// Rest of the form isn't relevant for this example
<Label htmlFor="ingredients" text="Ingredienser" />
<div>
{getValues().ingredients.map(() => (
<div key={Math.random()}>
<input
type="text"
placeholder="Lys rom" />
<input
type="text"
placeholder="60" />
<select>
<option value="ml">ml</option>
<option value="dashes">stænk</option>
<option value="stk">stk</option>
</select>
</div>
))}
<div>
<button
type="button"
onClick={onAddIngredient}>
Tilføj ingrediens
</button>
</div>
</div>
</div>
<div>
<input type="submit"
value="Gem" />
</div>
</form>
</div>
</>
);
}
export default CreateCocktail;
3
Answers
First, you need to use
setValue
method to update form values notgetValues.push()
. SecondgetValues
will not trigger rerender, you need to usewatch
and pass the name of field that you want to listen i.e.ingredients
. If you do not pass the name towatch
then it returns all form values.Here’s the complete code.
Problem
This code uses
getValues
incorrectly, it cannot update hookform values viagetValues
(get is only for fetching current values), we must usesetValue
to change form values.Furthermore, if you want to get real-time updates from form values you cannot use
getValues
(this only gets the current value when called but does not update when changed), you must usewatch
for this instead.Refer to the code below for a working example of what you are trying to achieve.
Solution
What you need is called
Dynamic Form
.Using
setValue
andgetValue
might be very tricky.You should use
useFieldArray
hook fromreact-hook-form
.I wrote something for you here so you can see the exact usage.
BTW this is a link to documentation.
And this is a link to a good video make by the creator of
react-hook-form
.Hope it helps.