React
useState
useState
is one of the basic hooks from React which allows you to define and use local states within a functional component. When the values of the states change, React will trigger a rerender process to update the DOM.
useState
has only one argument, and it is the initial value of the state. useState
returns an array containing 2 values, the first value is the current state and the second is a function to update it.
// Calling signature
const [state, stateUpdaterFunction] = useState(initialStateValue)
Declare State Variable
Declaring a state variable is as simple as calling useState
with some initial state value.
function Count() {const [count] = useState(100)return (<span>State variable is {count}</span>)}
Update State Variable
To update a state variable, simply pass a new value to the stateUpdaterFunction
returned by the useState
invocation.
// Calling signature// const [state, stateUpdaterFunction] = useState(initialStateValue)function Count() {const [count, setCount] = useState(1)const handleClick = () => setCount(count + 1)return (<div><div>Current count is {count}</div><Button onClick={handleClick}>+</Button></div>)}
Multiple State Variables
Multiple state variables may be used and updated from within a functional component
// Calling signature// const [state, stateUpdaterFunction] = useState(initialStateValue)function Count() {const [count, setCount] = useState(1)const [isByTwo, setByTwo] = useState(false)const handleClick = () => setCount(count + (isByTwo ? 2 : 1))const handleTick = (e) => setByTwo(e.target.checked)return (<div><div>Current count is {count}</div><Button onClick={handleClick}>+{isByTwo ? 2 : 1}</Button><br /><input type="checkbox" onChange={handleTick} /> By 2</div>)}
Object State Variable
You can put any type of data in a state variable. But note that when passing new values to the stateUpdaterFunction
, React will replace the old value with the new one, NOT merge. Check out the example below for how to merge a new object value and the old one
function UserInfo() {const [user, setUser] = useState({ name: "Henry", age: 18 })const handleClickWithoutMerge = () => setUser({ age: user.age + 1 })// Object merging using spread operatorconst handleClickMerge = () => setUser({...user,age: user.age + 1})return (<div><div>My name is {user.name || "Unknown"}</div><div>I'm {user.age} years old</div><div><Button onClick={handleClickMerge}>Get older (merge)</Button><Button onClick={handleClickWithoutMerge}>Get older (without merge)</Button></div></div>)}
Warning
React uses shallow comparison when checking for state differences. This means states that have Reference Types like Object or Array will be considered the same if they are pointing to the same memory location, even if their inner props or children are different. And React will not trigger a re-rendering.
function UserInfo() {const [user, setUser] = useState({ name: "Henry", age: 18 })const handleClickNotRerender = () => {const newUser = user // Not new at allnewUser.age = user.age + 1setUser(newUser)}// Creating new object and copy old propertiesconst handleClick = () => setUser({...user,age: user.age + 1})return (<div><div>My name is {user.name || "Unknown"}</div><div>I'm {user.age} years old</div><div><Button onClick={handleClickNotRerender}>Get older (not rerender)</Button><Button onClick={handleClick}>Get older</Button></div></div>)}
Initialize State Value from Function
As mentioned, the initialStateValue
passing to the useState
hook can be anything, even a function. In that case, React will call the function and the returned value will be used as the initial value.
function TimestampCount() {const [count, setCount] = useState(() => {const now = Date.now()return now})const handleClick = () => setCount(count + 1)return (<div><div>Current count is {count}</div><Button onClick={handleClick}>+</Button></div>)}
Update State Value using Function
The new value that we pass to the stateUpdaterFunction
can be also a function. The function will take in the previous state value as the argument, React will call the function and the returned value will be used as the new value. This is ideal when we want to calculate a new state value that depends on the previous one. Check out the example below
function Count() {const [count, setCount] = useState(1)const handleDecrease = () => setCount((previousValue) => {return previousValue - 1})const handleIncrease = () => setCount((previousValue) => {return previousValue + 1})return (<div><div>Current count is {count}</div><Button onClick={handleDecrease}>-</Button><Button onClick={handleIncrease}>+</Button></div>)}
Best Practice
Because state value updating is Asynchronous, if the new state value is based on the previous one, you should always use the functional state updater. This will guarantee the previous states always have the correct values in case you have to call the stateUpdaterFunction
multiple times
function Count() {const [count, setCount] = useState(1)const handleIncreaseBy2Fail = () => {// current count = 1setCount(count + 1)// current count still = 1setCount(count + 1)}const handleIncreaseBy2 = () => {// previousCount = 1setCount((previousCount) => previousCount + 1)// previousCount = 2setCount((previousCount) => previousCount + 1)}return (<div><div>Current count is {count}</div><Button onClick={handleIncreaseBy2Fail}>+2 (Fail)</Button><Button onClick={handleIncreaseBy2}>+2</Button></div>)}