I am trying to build a small recipe app. One feature of the app is saving user recipes, including ingredient/qty/measurement.
I need to wrap up the inputted ingredients into an array of objects to send to server but right now my setIngredientList
only works for the first two ingredients a user inputs.
When a user tries to add a third ingredient it just mirrors the data from the second input (and fills the third input's fields with the same data as the second input). It is like the second inputs and any subsequent input mirror each other.
I believe the problem is init
is not clearing properly (it seems to clear after the first ingredient is added allowing the second one to be added, but then it does not clear for the next ingredients.
I'm not sure the proper way to make sure this happens so multiple ingredients can be added.
Here is my code:
const init = {
ingredient_name: '',
quantity: '',
measure: '',
}
export default function Recipe() {
const [name, setName] = useState('')
const [ingredientList, setIngredientList] = useState([
{
ingredient_name: '',
quantity: '',
measure: '',
},
])
const handleChange = (e, i) => {
const { name, value } = e.target
setIngredientList((prevState) => {
const newIngredientList = [...prevState]
newIngredientList[i][name] = value
return [...newIngredientList]
})
}
return (
<div>
<div className="recipe-form-container">
<form className="recipe-form">
[...]
</div>
{ingredientList.map((list, i) => (
<div key={i} className="ingredient-triad">
<input
className="ingredient"
name="ingredient_name"
type="text"
value={list.ingredient_name}
onChange={(e) => handleChange(e, i)}
></input>
<input
className="quantity"
name="quantity"
type="text"
value={list.quantity}
onChange={(e) => handleChange(e, i)}
></input>
<select
className="dropdown"
name="measure"
id="measure"
value={list.measure}
onChange={(e) => handleChange(e, i)}
>
<option value="" disabled>
--none--
</option>
<option value="cup">cup</option>
</select>
<button
onClick={(e) => {
console.log(init)
setIngredientList((prev) => [...prev, init])
e.preventDefault()
}}
>
Add
</button>
</div>
))}
</form>
</div>
</div>
)
}
CodePudding user response:
Classical object reference issue.
Use the below code it will work fine.
Previously, you pass the same init
object for multiple rows,
which is why you got that result. Instead of doing that, when the user clicks 'add' button then add a new Object to your state which is derived from your init
object. Here I just clone the init object and then set the state.
<button
onClick={(e) => {
console.log(init);
setIngredientList((prev) => [...prev, { ...init }]);
e.preventDefault();
}}
>
CodePudding user response:
Sounds like you wanted advice and can prob find the solution yourself, but what i would do this code.
Move e.preventDefault()
above setIngredientList
.
Create an Ingredient
class with the logic within - ingredientList
and pass state to Ingredient
, imo the list should not not be concerned with Ingredient, it just lists a thing.
Then what is onClick doing - init
- its the initial value isn't it, so I think that with your onClick you are overwriting prev with the value that is outside the component - should it be outside or is it some state - it holds a value yeah - so its state i believe? Checkout react tools profiler so see exactly what is happening.
So what is prev we are over writing it, but what is it? Isn't the first arg the click event? So I think you are overwriting the click event with the init state - which is outside the component.