When we want to display on screen some information that will be modified as the user interacts with our application, it is common to think about storing it in a state variable, which will be updated according to this interaction.
However, if this information depends on an existing state, some precautions can be taken to prevent bugs and performance issues.
Common mistakes when displaying information derived from an existing state
To exemplify, let's imagine a shopping cart component used to sale event tickets, where whenever the user changes the desired amount of tickets, the total purchase price should also be updated.
❌ Create a new state and update it whenever the original state is modified
In this scenario, inconsistencies in the information may occur, since it will be necessary to manually update the value of one state whenever the other one changes.
1"use client";2import { useState } from "react";34import { currencyFormatter } from "@/utils/currency";56const TICKET_PRICE = 15.0;78export const Cart = () => {9 const [ticketAmount, setTicketAmount] = useState(0);10 const [totalPrice, setTotalPrice] = useState(0);1112 const handleTicketIncrement = () => {13 const newTicketAmount = ticketAmount + 1;14 setTicketAmount(newTicketAmount);15 setTotalPrice(newTicketAmount * TICKET_PRICE);16 };1718 const handleTicketDecrement = () => {19 const newTicketAmount = ticketAmount - 1;20 setTicketAmount(newTicketAmount);21 // Haven't updated totalPrice state22 };2324 return (25 <>26 <button onClick={handleTicketDecrement}>-</button>27 <span>Tickets: {ticketAmount}</span>28 <button onClick={handleTicketIncrement}>+</button>29 <span>Purchase price: {currencyFormatter(totalPrice)}</span>30 </>31 );32};33
In the code snippet above, on line 21, the totalPrice
state was not updated in the handleTicketDecrement
function, which handles decrementing the tickets counter. Thus, its value will get outdated when the function is invoked, causing incorrect information to be displayed on the screen.
❌ Create a new state and update it as a side effect of updating the original state
With the implementation below, every time the value of the ticketAmount
state changes, the totalPrice
state will automatically update.
1"use client";2import { useEffect, useState } from "react";34import { currencyFormatter } from "@/utils/currency";56const TICKET_PRICE = 15.0;78export const Cart = () => {9 const [ticketAmount, setTicketAmount] = useState(0);10 const [totalPrice, setTotalPrice] = useState(0);1112 const handleTicketIncrement = () => {13 setTicketAmount(ta => ta + 1);14 };1516 const handleTicketDecrement = () => {17 setTicketAmount(ta => ta - 1);18 };1920 useEffect(() => {21 setTotalPrice(ticketAmount * TICKET_PRICE);22 }, [ticketAmount]);2324 return (25 <>26 <button onClick={handleTicketDecrement}>-</button>27 <span>Tickets: {ticketAmount}</span>28 <button onClick={handleTicketIncrement}>+</button>29 <span>Purchase price: {currencyFormatter(totalPrice)}</span>30 </>31 );32};33
Although totalPrice
has the correct value now, each new update to the ticketAmount
state will result on an unnecessary re-render of the component.
This happens because useEffect
will cause totalPrice
to be updated only in a new rendering flow, which can lead to performance issues in applications with a large amount of state updates.
setTicketAmount(ta => ta + 1) is a way to update the state based on its previous value
The best way to display information derived from an existing state
✅ Calculate it during component rendering
Ideally, the information should be calculated while the component is rendering. This ensures that it will be always up-to-date, as well as avoiding unnecessary re-renders, which optimizes application performance and helps bug prevention.
1"use client";2import { useState } from "react";34import { currencyFormatter } from "./utils/currency";56const TICKET_PRICE = 15.0;78export const Cart = () => {9 const [ticketAmount, setTicketAmount] = useState(0);10 const purchasePrice = ticketAmount * TICKET_PRICE;1112 const handleTicketIncrement = () => {13 setTicketAmount(ta => ta + 1);14 };1516 const handleTicketDecrement = () => {17 setTicketAmount(ta => ta - 1);18 };1920 return (21 <>22 <button onClick={handleTicketDecrement}>-</button>23 <span>Tickets: {ticketAmount}</span>24 <button onClick={handleTicketIncrement}>+</button>25 <span>Purchase price: {currencyFormatter(purchasePrice)}</span>26 </>27 );28};29
In the example above, purchasePrice
is calculated during component rendering, based on the current value of ticketAmount
. Thus, whenever ticketAmount
is updated, purchasePrice
will also update accordingly, without the need for additional state updates or unnecessary re-renders.
Conclusion
Summarizing, when dealing with data derived from an existing state in React, it's important to avoid creating new states whenever possible. Instead, aim to compute them directly during component rendering, thereby improving the quality of your code.