useState with an object
Editor’s note: This article was last updated by Nefe James on 2 February 2024 to explore object restructuring using the useState Hook. It elaborates on strategies for updating object states and includes the application of useState with arrays of objects.
React was created to help developers easily and efficiently perform Document Object Model (DOM) manipulations in their browsers rather than the conventional way using vanilla JavaScript.
One of React’s most commonly used Hooks is useState, which manages states in React projects as well as objects’ states. With an object, however, we can’t update it directly or the component won’t re-render.
To solve this problem, we’ll look at how to use useState when working with objects, including the method of creating a temporary object with one property and using object destructuring to create a new object from the two existing objects.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
To understand how to manage an object’s state, we must update an item’s state within the object.
In the following code sample, we’ll create a state object, shopCart, and its setter, setShopCart. shopCart then carries the object’s current state while setShopCart updates the state value of shopCart:
const [shopCart, setShopCart] = useState({});
let updatedValue = {};
updatedValue = {"item1":"juice"};
setShopCart(shopCart => ({
...shopCart,
...updatedValue
}));
We can then create another object, updatedValue, which carries the state value to update shopCart.
By setting the updatedValue object to the new {"item1":"juice"} value, setShopCart can update the value of the shopCart state object to the value in updatedValue.
To take a step forward, we can create a function to wrap the removed logic triggered by submitting a form:
import React, { useState } from 'react';
function App() {
const [shopCart, setShopCart] = useState({item1:"Juice"});
const handleChange = (e) => {
let updatedValue = {};
updatedValue = {item1:e.target.value};
setShopCart(shopCart => ({
...shopCart,
...updatedValue
}));
}
return (
<div classname="App">
<h3>useState with object in React Hooks - <a href="https://www.logrocket.com">LogRocket</a></h3>
<br/>
<label>Name:</label>
<input type="text" name="item1" defaultValue={shopCart.item1} onChange={(e) => handleChange(e)}/>
<br></br>
<label>Output:</label>
<pre>{JSON.stringify(shopCart, null, 2)}</pre>
</div>
);
}
export default App;
By wrapping the logic we covered earlier in a handleChange function, we can handle any changes in the input field.
Within the input field, let’s set the value of the input element to the value of item1 in the shopCart object, which allows users to see its value as they make changes to it from the input field.
Next, let’s add the onChange event handler to each input element, ensuring the handleChange function triggers when we make any changes in the input field. Finally, we can display the current state of the shopCart object as we make changes to it:

The same technique can be used to remove an item from an object:
const [shopCart, setShopCart] = useState({item1:"Juice", item2: "Icrecream"});
let copyOfObject = { ...shopCart }
delete copyOfObject['propertyToRemove']
setShopCart( shopCart => ({
...copyOfObject
}));
By creating a copy of the shopCart state object, we can delete an item from its copy, copyOfObject. We can then set the state of the original object, shopCart, to the value of the copied object, copyOfObject, using the setter object, setShopCart, which we defined earlier.
To take a step further, we can create a function to wrap the logic, which is triggered by clicking a button:
import React, { useState } from 'react';
function App() {
const [shopCart, setShopCart] = useState({item1:"Juice", item2:"Icrecream"});
const handleClick = (item_id,e) => {
let copiedShopCart = {...shopCart};
delete copiedShopCart[item_id];
setShopCart( shopCart => ({
...copiedShopCart
}));
console.log(shopCart);
}
return (
<div classname="App">
<h3>useState with object in React Hooks - <a href="https://www.logrocket.com">LogRocket</a></h3>
<br/>
1.{shopCart.item1}
<button onClick={(e) => handleClick("item1",e)}>delete</button>
<br/>
<br/>
{shopCart.item2}
<button onClick={(e) => handleClick("item2",e)}>delete</button>
<pre>{JSON.stringify(shopCart, null, 2)}</pre>
</div>
);
}
export default App;
Again, we wrap the logic we covered earlier in the handleClick function, which handles any click events from the buttons attached to it.
This allows us to list both items in the shopCart object and create a button for each item.
By attaching the handleClick function to the buttons using the onClick event, we can pass each item’s ID in the shopCart object to the handleClick function to detect which item to delete when the function triggers:

There are three methods for updating an object’s state:
Let’s explore these methods in detail and see practical examples of how they work.
Normal state updates involve using the setState function to directly update a state’s value. It is the easiest way of handling state updates and is commonly used for basic state management operations like the following:
The code snippet below is an example of a normal state update in action. In this scenario, clicking the Increment button will increase the count by 1, from 0 to 1 in this case:
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); //count remains "0"
}
return (
<div>
<p>count is {count}<p/>
<button onClick={handleClick}>Increment</button>
</div>
);
}
export default App;
While this works, there’s a slight challenge with performing normal state updates. If we log the value of count to the console, we’ll see 0 instead of 1.
This happens because React state updates happen asynchronously, which means that setName will not update the name variable in the already running code. Learn more about why React doesn’t update state immediately.
Working with normal state updates can lead to stale state values and inconsistent updates. We can address this issue by using functional state updates. This method allows us to pass a callback function to setState instead of a value.
Here’s an update of the counter example, but this time with a functional update:
function handleClick() {
setCount(prevCount => prevCount + 1);
console.log(count); //count will update to +1 after every button click
}
It is better to use functional updates instead of state updates when we need to update a state “B” based on a previous state “A.” Functional updates ensure that we are always working with the latest state instead of a stale state.
The spread operator (...) is a JavaScript feature for expanding an iterable (like an array or object) into individual elements. We can use it to create a new state based on the previous one without directly mutating the original state.
Consider the following state:
const [state, setState] = useState({ a: 1, b: 2 });
We can use the spread operator to add a new value to the state or update one of its existing properties without directly mutating it:
const [state, setState] = useState({ a: 1, b: 2 });
// Use the spread operator to create a new object with the existing data and update the state
function updateState(){
setState(prevState => ({ ...prevState, c: 3 }));
//new state becomes { a: 1, b: 2, c: 3 }
};
In this example, setState(prevState => ({ ...prevState, c: 3 })) creates a new object by spreading the existing state and adding a new property. This ensures the immutability of the previous state.
We could have also updated the value of an existing property instead of adding a new one:
function updateState(){
setState(prevState => ({ ...prevState, b: "Mary" }));
//new state becomes { a: 1, b: "Mary" }
};
We can also use the spread operator to update array-based states. Here’s an example:
const [state, setState] = useState(["a", "b", "c"]);
function updateState(){
setState(prevState => ({ ...prevState, "d" }));
//new state becomes ["a", "b", "c", "d"]
};
The spread operator also proves useful when working with nested state objects. Here’s how to update some state that contains nested properties:
const [state, setState] = useState({
age: 23,
contactInfo: {email: "[email protected]", phoneNumber "+28125678"}
});
function handleNestedUpdate(){
// Use the spread operator to update the 'nested' property without affecting other properties.
setState(prevState =>
({ ...prevState, prevState.contactInfo.email: "[email protected]" }));
The code above ensures that only the contactInfo.email nested property is updated, and the rest of the state remains unchanged.
Benefits of using the spread operator to update React state include:
useState Hook with arrays of objectsLet’s explore how to update an array of objects in the React state. Consider the following state:
const [names, setNames] = useState([
{ id: 1, name: "John" },
{ id: 2, name: "Mary" },
]);
We can update the state by mapping through the array and updating a specific object based on its id:
function updateNames(id, updatedName) {
// 1. Map through all names to create new array
const updatedName = names.map((name) => {
// 2. Check if current name's id matches
if (name.id === id) {
// 3. Return new object with updated name
return { ...name, name: updatedName };
}
return name;
});
// 4. Update state with new array
setNames(updatedName);
}
Here’s the complete code:
import { useState } from "react";
function App() {
const [names, setNames] = useState([
{ id: 1, name: "John" },
{ id: 2, name: "Mary" },
]);
function updateNames(id, updatedName) {
const updatedName = names.map((name) => {
if (name.id === id) {
return { ...name, name: updatedName };
}
return name;
});
setNames(updatedName);
}
return (
<div>
<pre>{JSON.stringify(names)}</pre>
<button onClick={() => updateNames(1, "Jane")}>Update Name</button>
</div>
);
}
While this works, there are some performance considerations to note when mapping over an array to update state like this:
Instead of mapping through the array, we can better optimize this by using the index to update the items in the array directly:
function updateNames(id, updatedName) {
// 1. Find index of object with matching id
const index = names.findIndex((name) => name.id === id);
// 2. Create a new array reference by spreading existing array. This is to avoid direct state mutation
const updatedNames = [...names];
// 3. Create new object with updated name
updatedNames[index] = { ...updatedNames[index], name: updatedName };
// 4. Update state with new array
setNames(updatedNames);
}
useStateObject destructuring is a great way to work with the state object returned by useState. It allows you to extract specific properties from the state object, making your code cleaner and more readable.
Here’s a simple example:
import React, { useState } from 'react';
function App() {
const [state, setState] = useState({
count: 0,
message: 'Hello, World!',
});
// Destructuring the state object to extract "count" and "message"
const { count, message } = state;
// Updating state using the spread operator
function incrementCount() {
setState(prevState => ({
...prevState,
count: prevState.count + 1
};
return (
<div>
<p>{message}</p>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment Count</button>
</div>
);
}
Without destructuring, we would have had to access the state values directly from the state object:
const [state, setState] = useState({
count: 0,
message: 'Hello, World!'
});
const message = state.message;
const count = state.count;
function incrementCount() {
setState(prevState => ({
...prevState,
count: prevState.count + 1;
};
We can also destructure a state’s object directly in the useState call:
function App() {
const [{ count, message }, setState] = useState({
count: 0,
message: 'Hello World'
});
function incrementCount() {
//increase the value of "count"
}
While this works and direct destructuring leads to more concise code, there are some downsides and tradeoffs to be aware of, including:
This article taught you how to use React’s useState Hook with objects. It covers techniques for updating and deleting object properties within the state, emphasizing the importance of immutability and state management. It offers practical examples for updating object properties, using techniques such as functional updates and the spread operator.
I recommend checking out this article to learn more about useState. If you have any questions, don’t hesitate to contact me on Twitter at @LordChuks3.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 22nd issue.

John Reilly discusses how software development has been changed by the innovations of AI: both the positives and the negatives.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
2 Replies to "Using React <code>useState</code> with an object"
Hey. You should wrap callbacks in useCallback, especially in real apps that become larger. Then, you’ll need to pass current state as dependency into that hook, which makes the approach almost obsolete.
The solution and important lesson: setState(current => ({…current, …next}))
Use a function to set the state, and you’ll get the current one for free, without it being a dependency of the callback hook.
let updatedValue = {};
updatedValue = {“item1″:”juice”};
why not in one statement :
let updatedValue = {“Item1″:”juice”}