mexicanChica
mexicanChica

Reputation: 69

React Hooks Form Handling: Update the state of an object with multiple string items and one array item

I'm learning React using hooks to make an application. I've been following a tutorial however I would like to make some changes to it. I have an object with multiple string items and one array item:

  const [recipe, setRecipe] = useState({
    title: '',
    description: '',
    ingredients: [],
    instructions: '',
    tags: 'none'
  }); 

The tutorial originally had all string items so the following code worked perfectly to update the state:

    setRecipe({ ...recipe, [e.target.name]: e.target.value });
  }

Example of one of the input fields which were all were similar.

          <input
            type='text'
            placeholder='Title'
            name='title'
            value={title}
            onChange={onChange}
          />

Now that i've changed one of the items to an array, it no longer works. I've tried several ways to do it, for example:

 const [test, setTest] = useState({
    ingredients: ['bread', 'milk', 'honey']
  });

 const [query, setQuery] = useState('');

  const updateQuery = e => {

    setQuery(e.target.value);
  };

  const addItem = e => {
    e.preventDefault();

    setTest(test => ({ ingredients: [...test.ingredients, query] }));
  };

return (

          <div>
            <button className='btn btn-light btn-block' onClick={addItem}>
              Add ingredient
            </button>
            <input
              type='text'
              placeholder='Description'
              name='ingredients'
              value={ingredients}
              onChange=(updateQuery)
            />
          </div>
          <div>
            {test.ingredients.map(data => (
              <ul key={data}>{data}</ul>
            ))}
          </div>

  );

I'm struggling to find a solution.

Help would be appreciated.

Upvotes: 1

Views: 3494

Answers (3)

Chris
Chris

Reputation: 993

The issue is with this line: setTest(test => ({ ingredients: [...test.ingredients, query] })

When working with react class components it would have been fine to set the state without spreading the entire previous state like this.setState({ ingredients: [...test.ingredients, query] }) because internally react already merges your new object you pass into this.setState with the previous state.

In react hooks, React.useState doesn't do the merging so you have to merge the previous state like setState(test => ({ ...test, ingredients: [...test.ingredients, query] }).

edit: Also you declared the variable test at the top of your component. I recommend to rename the test argument inside setTest to something else.

Upvotes: 0

Incepter
Incepter

Reputation: 2948

The code you provided needs some formatting (aka: onChange={onChange} rather than onChange and your input value to have query rather than ingredients)

Also, you were not retaking the old state when editing it.

I added to your state a non array integer field

const [test, setTest] = React.useState({
    nonArray: 0,
    ingredients: ["bread", "milk", "honey"]
  });

And then I changed you addItem to look like:

const addItem = e => {
    e.preventDefault();
    setTest(old => ({ ...old, ingredients: [...old.ingredients, query] }));
    setQuery("");
  };

And it seems to be working like a charm.

Please check this working codesandbox that contains basically your code.

Upvotes: 3

Stark Jeon
Stark Jeon

Reputation: 1145

The way I see it, you need use useCallback in addItem, updateQuery like

const updateQuery = useCallback(e => {

    setQuery(e.target.value);
  },[]);

const addItem = useCallback(e => {
    e.preventDefault();

    setTest(test => ({ ingredients: [...test.ingredients, query] }));
  },[query]);

It is hooks principle, if you want to understand this principle, you can search Principle of useCallback.

If you want to know easy way, I can tell you one thing. In when render component, addItem has query -> state change -> but addItem's query can't change

Upvotes: 0

Related Questions