Reputation: 69
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
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
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
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