deadant88
deadant88

Reputation: 1008

React input field mirrors the data from previous input

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>
  )
}

Upvotes: 1

Views: 324

Answers (2)

Jeremy
Jeremy

Reputation: 1568

Sounds like you wanted some advice and can prob find the solution yourself, but what i would do to this code:

Move e.preventDefault() above setIngredientList.

Create an Ingredient class with the state / logic within and pass state to Ingredient, the list should not not be concerned with an Ingredient, it just lists.

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 - so it should be an initial state of an ingredient? Checkout react tools profiler so see exactly what is happening on your events.

So what is prev we are over writing it, but what is it? Isn't the first arg on onClick the click event? So I think you are overwriting the click event with the init state - which is outside the component - a few things to tweak for you (if you like), hope it helps.

Upvotes: 0

SKG
SKG

Reputation: 64

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();
                }}
              >

Upvotes: 1

Related Questions