Chris M.
26 Nov 2021
•
3 min read
React is essentially a component-based library. You can think of a component as a function which takes in some properties as input and outputs UI element(s). Now a component will need to manage it's own state. So, let's talk about the useState() hook.
Basically, Hooks are a feature of React that allow us to hook functionality when using functional components. Here is how a basic functional component would look like;
import React, {useState} from "react";
const MyTestComponent = () => {
const [somevalue, setSomeValue] = useState(100);
return (
<div>
<p>{value}</p>
<button onClick={() => setSomeValue((somevalue + 1))}>Increment Some Value By 1</button>
</div>
);
};
The useState() hook allows us to set individual states. When we call useState(), we are kind of declaring a state variable and that state is actually retained between the different function calls. Thus, useState() gives a guarantee that the same value will be retained across renders. React has the ability to retain state values internally ensuring that the updated value is returned each time the function component runs.
The useState() hook accepts an input parameter which is used to set the initial state variable.
import React, {useState} from "react";
const MyTestComponent = () => {
const [somevalue, setSomeValue] = useState(100);
};
In the example above, the state value would get initialised to 100.
If we pass a function to React’s useState(), it would call that function and use the return value to initialize the state.
import React, {useState} from "react";
const MyTestComponent = () => {
console.log(“Example of passing function to useState…”)
const functionForInitialState = () => 100;
const [somevalue, setSomeValue] = useState(functionForInitialState);
};
This way enables us to use lazy initialisation.
When we use a function, it ensures that the state value is computed only once. This also helps in terms of performance improvement, specifically if the computation is expensive.
Let us now look at the other variant from useState() hook that supports a functional variant. We will look at some examples which uses the standard update for state(s) as well as the functional update.
export default function App() {
const [counter, setCounter] = useState(100);
const firstCallback = () => setCounter((count) => count + 10);
const secondCallback = useCallback(() => {
// counter state is re-enclosed within our callback scope ensuring that the state is updated
setCounter(counter + 10);
}, [counter]);
const thirdCallback = useCallback(() => {
// “counter” value is stale from the initial render
setCounter(counter + 10);
}, []); // <-- Here linter also warns about the missing dependency array
const fourthCallback = useCallback(() => {
// This is an example of functional state update avoiding stale state enclosures
setCounter((count) => count + 10);
}, []);
return (
<div className="App">
<h1>Counter: {counter}</h1>
<div>
<button type="button" onClick={ firstCallback}>
Increment counter
</button>
Example of functional state update
</div>
<div>
<button type="button" onClick={ secondCallback}>
Increment counter
</button>
Example of useCallback with dependency of state to re-enclose state back
</div>
<div>
<button type="button" onClick={ thirdCallback}>
Increment counter
</button>
Example of useCallback without any dependency. This is a standard state update and thus it is a stale state, which would always start from 100
</div>
<div>
<button type="button" onClick={ fourthCallback}>
Increment counter
</button>
Example of useCallback without dependency. This is a functional update and thus it ensures that the state is not stale.
</div>
</div>
);
}
We observed that if the “useCallback” hook is used with no dependencies and if it references any local state, the value seen inside the callback is actually state i.e. from the render cycle that the callback got created. If we try to filter this array, it would always refer to the same initial state, which is stale. Thus, by specifying state i.e. counter here in the array of dependencies, we can kind of enclose the updated “counter” value inside useCallback().
Thus, we can see that for any state updates that rely on the previous state value, we should use the functional variant of state update. The counter example in this case is one of the use cases in React and we need to know the previous count value and then add 10 in this case to return the new state. There can be other use cases like updating complex arrays/objects and where we would need to shallow copy the previous state value into new reference for the piece of state that we are going to update. If we simply want to replace the object/array and don't actually need the previous state, the regular state update should work fine as well.
Here is another example to show the difference between regular and functional variant of state updates.
import React, { useState } from "react";
import "./styles.css";
const App = () => {
const [counter, setCounter] = useState(100);
/**
* counter + 10 handler using the normal state update
*/
const handler1 = () => {
// assume counter equals some number num
setCounter(counter + 10); // update queued, counter === num, counter = num + 10 setCounter(counter + 10); // update queued, counter === num, counter = num + 10 setCounter(counter + 10); // update queued, counter === num, counter = num + 10 // when processed the counter will be num + 10
};
/**
* counter + 10 handler using functional state update
*/
const handler2 = () => {
// assume “counter” equals some number num
setCounter((counter) => counter + 10); // update is queued, counter === num + 0, counter = previouscounter + 10
setCounter((counter) => counter + 10); // update is queued, counter === num + 1, counter = previouscounter + 10
setCounter((counter) => counter + 10); // update is queued, counter === num + 2, counter = previouscounter + 10
// Here for each setState call, it uses the prior result and then updates the state to the new value // counter will be num + 10 + 10 + 10 OR num + 30
};
return (
<div className="App">
<h1>useState Example with Counter</h1>
<h2>Regular State Update V/s Functional State Update</h2>
counter: {counter}
<div>
<button type="button" onClick={handler1}>
+10 via the normal state update
</button>
<button type="button" onClick={handler2}>
+10 via the functional state update
</button>
</div>
</div>
);
};
export default App;
Chris M.
I love doing frontend development and am a fan of cutting edge frameworks like React, Angular, Backbone, etc
See other articles by Chris
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!