useState
with an objectEditor’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.
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); }
useState
Object 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>
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 nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
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”}