The useState hook allows us to make our function components stateful.
Create and Initialize State
When called, useState
returns an array of two items. The first being our state value and the second being a function for setting or updating that value. The useState
hook takes a single argument, the initial value for the associated piece of state, which can be of any Javascript data type.
We assign these two returned values to variables using array destructuring.
import React, { useState } from 'react';
const Component = () => {
const [value, setValue] = useState(initial value)
...
Since array elements have no names, we can name these two variables whatever we want. The general convention for declaring the name of your updater function is to begin with set and end with the name of your state variable, so [value, setValue]
. The initial state argument passed in will be the value assigned to the state variable on the first render.
A Few Examples of State with Various Data Types
Each piece of state has its own call to useState
and its own variable and function for setting/updating it.
const [count, setCount] = useState(0)
const [color, setColor] = useState('#526b2d')
const [isHidden, setIsHidden] = useState(true)
const [products, setProducts] = useState([])
const [user, setUser] = useState({
username: '',
avatar: '',
email: '',
})
Count is a number that we plan to increment or decrement, the initial value being 0. Color's initial value is a string holding the hash code with a default value of green. isHidden is a boolean with the initial value of true which we can assume describes the visibility of something in the DOM that will toggle between hidden and visible. Products' initial value is an empty array that we plan on populating with a list of products most likely fetched from an API. User is an object with several properties, all of which default to empty strings.
Initializing Expensive State
If your value is expensive to compute, like having to filter and manipulate a list of items, you can wrap the initialization in a function so that the useState
will only call the function once rather than on every render.
const [filteredList, setFilteredList] = useState(() => listOf10MillionItems.filter())
Updating Primitive Types
Updating state variables with useState
always replaces the previous state. This means that updating primitive types (strings, booleans, numbers) is simple because their values are replaced rather than mutated.
Here is the classic and simple counter component example. We want to increment or decrement a number stored in state and display that number to the user or reset that number back to 0.
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0)
const increment = () => setCount(count + 1)
const decrement = () => setCount(count - 1)
const reset = () => setCount(0)
return (
<div className='counter'>
<p className='count'>{count}</p>
<div className='controls'>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
</div>
)
}
export default Counter
Updating Arrays and Objects
When updating arrays or objects in state with useState
, you must remember to pass the entire object or array to the updater function as the state is replaced, NOT merged as with the setState
method found in class-based components.
Arrays
const [items, setItems] = useState([])
// Completely replaces whatever was stored in the items array
setItems([{item1}, {item2}])
// Don't use JS array methods such as pop, push, shift, unshift
// as these will not tell React to trigger a re-render.
items.push({item3})
// Instead, make a copy of the array then add your new item onto the end
setItems([...items, {item3}])
// To update an item in the array use .map.
// Assumes each array item is an object with an id.
setItems(
items.map((item, index) => {
item.id === id ? newItem : item
})
)
Objects
const Person = () => {
const [person, setPerson] = useState({
firstName: '',
lastName: ''
});
const handleChange = (e) => {
setPerson({
...person,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e) => {
e.preventDefault()
// Form submission logic here.
}
return (
<form>
<label htmlFor='first'>
First Name:
<input
id='first'
name='firstName'
type='text'
value={person.firstName}
onChange={handleChange}
/>
</label>
<label htmlFor='last'>
Last Name:
<input
id='last'
name='lastName'
type='text'
value={person.lastName}
onChange={handleChange}
/>
</label>
<button type='submit' onClick={handleSubmit}>Submit</button>
</form>
);
};
In the above example, the handleChange
function calls setPerson
and passes in the person object from state using the spread operator with ...person
. Without passing in the existing person object stored in state, the entire object would be overwritten anytime one of the input values changed.
Nested Objects and Arrays
To updated nested objects and arrays, each level needs to be copied and updated immutably as with the above examples.
const [people, setPeople] = useState({
jerry: {
firstName: 'Jerry',
lastName: 'Garcia',
address: {
street: '710 Ashbury Street',
city: 'San Francisco',
state: 'CA',
zip: '94117'
}
},
jim: {
firstName: 'Jim',
lastName: 'Morrison',
address: {
street: '8021 Rothdell Trail',
city: 'Los Angeles',
state: 'CA',
zip: '90046'
}
}
})
// Jerry is gonna move next door
setPeople({
// Copy people
...people,
// Overwrite person you want to update
jerry: {
// Copy Jerry's existing properties
...people.jerry,
// Overwrite Jerry's address
address: {
// Copy everything over from Jerry's original address
...people.jerry.address,
// Update the street
street: '712 Ashbury Street'
}
}
})
Complex State
If you have complex state with multiple values, storing them in useState
can become cumbersome. Another hook called useReducer
is more suited to managing state with multiple values.
Thanks for reading!
Top comments (6)
I need some help while using useState([]), with array of objects.
Hey @mayursarode4 ! Sorry I didn't see your comment. I'm sure you figured out your issue a long time ago, but I'm happy to help if you still need assistance.
@brettblox thanks for the helpful write-up on useState and useReducer hooks. I needed to manage complex state for a lengthy form component so I went with useReducer which was much more manageable than having multiple states all over the place.
hey fellows, I am having a bunch of confusion that is,
I am working in react native and the problem is that I want to update an array of objects using the useState but cant do it. Can anyone tell the way so that the whole object is updated. I have attach a picture
Yo! Sorry I didn't see your comment. I'm sure you figured out your issue a long time ago, but I'm happy to help if you still need assistance.
Thank you so much. I feel confident to use it.