Issue
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;
Solution
Problem
The code you provided uses getValues
incorrectly, as it cannot update hookform values via getValues
(get is only for fetching current values). Instead, we must use setValue
to change form values.
Furthermore, if you want to get real-time/onChange updates from hook-form values you cannot use getValues
(this only gets the current value when called but does not update when changed), you must use watch
for this instead.
Refer to the code below for a working example of what you are trying to achieve.
Solution
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;
}
export const CreateCocktail = () => {
const { getValues, handleSubmit, setValue, watch} = useForm<Cocktail>({
defaultValues: {
name: '',
description: '',
imageUrl: '',
ingredients: [
{name: '', quantity: '', unit: ''}, {name: '', quantity: '', unit: ''}
]
}
});
//create a watch for ingredients, you could also pass a string array to watch multiple values if needed
const IngridientsList = watch('ingredients')
//update item on click via setValue
const onAddIngredient = () => {
setValue('ingredients', [...getValues('ingredients'), {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)} >
<div>
{IngridientsList.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>
<input type="submit"
value="Gem" />
</div>
</form>
</div>
</>
);
}
export default CreateCocktail;
Answered By - Kyle Xyian Dilbeck
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.