TL;DR
It's recommended to use Functional State Update in React.
Intro
Lately, i've been working on React stuff. And once i made pr, what contains code like the one below
const [ open , setOpen ] = React . useState ( false ) ;
setOpen ( ! open ) // 2
My co-worker pointed out that it'is better to write code this way:
setOpen ( prev => ! prev ) // 1
I’m just curious about the difference between these two cases, so I dug into it and searched online. Some of the articles said that functional state updates have many advantages:
Consistency and Reliability
Performance Optimization
Readability and Maintainability
Emmm, it seems kind of useless. The main reason we should use Functional State Update is to avoid race conditions.
Functional State vs. Direct Update
All of us should keep in mind that setState in React is asynchronous . So if we don't use it correctly , it may cause unexpected problem.
Example
import React , { useState } from 'react' ;
export function App ( props ) {
const [ counter , setCounter ] = useState ( 0 ) ;
return (
< div className = 'App' >
< h1 > ${ counter } </ h1 >
< button
onClick = { ( ) => {
setCounter ( counter + 1 ) ;
setCounter ( counter + 1 ) ;
} } >
Add
</ button >
</ div >
) ;
}
Let's run the example
directly-update.mp4
the result is not what we expected. To resolve this issue, what we need to do is use Functional State Update
setCounter ( ( prevCount ) => prevCount + 1 ) ;
setCounter ( ( prevCount ) => prevCount + 1 ) ;
Same here, let's run the code
functional-state-update.mp4
Is that it? Wait, how could you such terrible code in real life ? Even a beginner could't write such bad code.
Real Life Case
import { useState , useEffect } from 'react' ;
import { AlertCircle } from 'lucide-react' ;
import { Alert , AlertDescription } from '@/components/ui/alert' ;
const AsyncCartExample = ( ) => {
const [ regularCart , setRegularCart ] = useState ( {
quantity : 0 ,
pendingRequests : 0 ,
successfulRequests : 0
} ) ;
const [ functionalCart , setFunctionalCart ] = useState ( {
quantity : 0 ,
pendingRequests : 0 ,
successfulRequests : 0
} ) ;
// Simulates an API call with random delay
const simulateAPICall = ( ) => {
return new Promise ( resolve => {
setTimeout ( ( ) => {
resolve ( ) ;
} , Math . random ( ) * 1000 ) ; // Random delay 0-1000ms
} ) ;
} ;
// ❌ Problematic approach - direct state updates
const addToRegularCart = async ( ) => {
setRegularCart ( {
...regularCart ,
pendingRequests : regularCart . pendingRequests + 1
} ) ;
await simulateAPICall ( ) ;
setRegularCart ( {
...regularCart , // Uses stale closure value!
quantity : regularCart . quantity + 1 ,
pendingRequests : regularCart . pendingRequests - 1 ,
successfulRequests : regularCart . successfulRequests + 1
} ) ;
} ;
// ✅ Better approach - functional updates
const addToFunctionalCart = async ( ) => {
setFunctionalCart ( current => ( {
...current ,
pendingRequests : current . pendingRequests + 1
} ) ) ;
await simulateAPICall ( ) ;
setFunctionalCart ( current => ( {
...current ,
quantity : current . quantity + 1 ,
pendingRequests : current . pendingRequests - 1 ,
successfulRequests : current . successfulRequests + 1
} ) ) ;
} ;
// Function to add multiple items quickly
const addMultipleItems = ( useRegular ) => {
const addFunction = useRegular ? addToRegularCart : addToFunctionalCart ;
// Add 5 items in rapid succession
for ( let i = 0 ; i < 5 ; i ++ ) {
addFunction ( ) ;
}
} ;
return (
< div className = "p-6 max-w-2xl mx-auto space-y-8" >
< Alert >
< AlertCircle className = "h-4 w-4" />
< AlertDescription >
Click "Add 5 Items" quickly multiple times to see the difference between regular and functional updates
</ AlertDescription >
</ Alert >
< div className = "grid grid-cols-2 gap-8" >
< div className = "p-4 border rounded-lg" >
< h2 className = "text-xl font-bold mb-4" > Regular Updates</ h2 >
< div className = "space-y-2" >
< div > Quantity: { regularCart . quantity } </ div >
< div > Pending: { regularCart . pendingRequests } </ div >
< div > Successful: { regularCart . successfulRequests } </ div >
< button
onClick = { ( ) => addMultipleItems ( true ) }
className = "bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
>
Add 5 Items
</ button >
</ div >
</ div >
< div className = "p-4 border rounded-lg" >
< h2 className = "text-xl font-bold mb-4" > Functional Updates</ h2 >
< div className = "space-y-2" >
< div > Quantity: { functionalCart . quantity } </ div >
< div > Pending: { functionalCart . pendingRequests } </ div >
< div > Successful: { functionalCart . successfulRequests } </ div >
< button
onClick = { ( ) => addMultipleItems ( false ) }
className = "bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded"
>
Add 5 Items
</ button >
</ div >
</ div >
</ div >
</ div >
) ;
} ;
export default AsyncCartExample ;
Go as routine
a-more-realistic-example.mp4
From the example above, i think it's quite clear why should we use Functional State Update rather than Direct Update.
Reference
Functional State Updates in React: A Guide to When and How to Use Them
Functional State Update in React
TL;DR
It's recommended to use Functional State Update in React.
Intro
Lately, i've been working on React stuff. And once i made pr, what contains code like the one below
My co-worker pointed out that it'is better to write code this way:
I’m just curious about the difference between these two cases, so I dug into it and searched online. Some of the articles said that functional state updates have many advantages:
Emmm, it seems kind of useless. The main reason we should use Functional State Update is to avoid race conditions.
Functional State vs. Direct Update
All of us should keep in mind that setState in React is asynchronous. So if we don't use it correctly , it may cause unexpected problem.
Example
Let's run the example
directly-update.mp4
the result is not what we expected. To resolve this issue, what we need to do is use Functional State Update
Same here, let's run the code
functional-state-update.mp4
Is that it? Wait, how could you such terrible code in real life ? Even a beginner could't write such bad code.
Real Life Case
Go as routine
a-more-realistic-example.mp4
From the example above, i think it's quite clear why should we use Functional State Update rather than Direct Update.
Reference