React Hooks - useState and useEffect
Introduction
React library allows us to create the UI components in two different ways. One, a class component and another one being, functional components.
While you can create components both the ways, only class based components are ‘stateful’ components. Meaning they can have state and with every change in state, the component can be re-rendered. Functional components did not have stateful-ness before the release of React version 16.8.
Functional components only return a JSX element, with no dynamic capabilities like re-rendering the same component when there is any change in the component. Thus making functional components as stateless components.
In the version 16.8, React introduced a new concept called ‘’hooks”. These made functional components achieve the stateful-ness like class components.
Before we move ahead, we need to take a look at general rules of react hooks.
- You can only have the hooks inside a functional component.
- Hooks cannot be declared inside the return statement of the component.
- Hooks should not inside the conditional statements and loops.
Of all the available hooks, useState and useEffect are the most used. Let's try to understand these two hooks in details and try to understand the use cases.
useState:
Every change in state of a component will trigger a re-render. useState makes a functional component have state and render it after every state change. You don’t have to worry about creating a state object and update it as needed, like you would do in a class based component. Just supply the initial state value to the hook, and we update the state with the function provided by useState hook.
Consider a common example like counter app.
import React from 'react';
function CounterApp() {
return (
<>
<button>-</button>
<div>0</div>
<button>+</button>
</>
)
}
We have a basic react app with two buttons, which increment or decrement a value. It is static and written as a functional component. Clicking on the buttons will not update the value of the div element.
To make this component update to our actions, we give the component a state. Depending on the state change, we will display the updated value of div.
First, we need to import the useState hook to our React app and give the initial state value to the hook.
import React, {useState} from 'react'; //hooks are named exports, use curly braces to import
function CounterApp() {
useState(0);
return (
<>
<button>-</button>
<div>0</div>
<button>+</button>
</>
)
}
useState hook is a function which returns an array of two values. The first element of the array is the state and the second element is a function, which helps us to update the state value as needed.
We will use the destructing, ES6 feature of JavaScript, to assign the array elements to variables.
The variables can be named anything, but the usual convention is to name it after a verb/action and use ‘set’ as prefix to the function name.
We will replace 0 with count, by this the div would display the value of count with corresponding state change.
import React, {useState} from 'react'; //hooks are named exports, use curly braces to import
function CounterApp() {
const [count, setCount] = useState(0);
return (
<>
<button>-</button>
<div>{count}</div>
<button>+</button>
</>
)
}
Our app would look something like this in the browser.
Let’s add the increment and decrement functionality to the buttons with the onClick events.
On every onClick event, we will update the count value using the setCount function. And for every new updated value of count, React would render the new value in the div element.
setCount can be invoked directly on the onClick event handler, or it can be invoked insider any other function which would be served to the event handler.
We will look at both the ways but invoking setCount directly on the decrement button and for increment button we will provide a custom increment function with setCount.
import React, { useState } from "react"; //hooks are named exports, use curly braces to import
function CounterApp() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<>
<button onClick={() => setCount(count - 1)}>-</button>
<div>{count}</div>
<button onClick={increment}>+</button>
</>
);
}
export default CounterApp;
Our counter app is ready. It would add up value with 1 on every click on increment button and decrease by 1 on every decrement button click.
Use the codeSandbox link here for the above counter app: https://codesandbox.io/s/priceless-bell-g5i5xq?file=/src/CounterApp.jsx
If you notice, the functions provided to the event handler are not self invoking functions. They are invoked only when there is a click event. Care should be taken to not write the self invoking functions on event handler, as they might cause infinite renders and app would crash.
//Not allowed
<button onClick={setCount(count - 1)}>-</button>
//Allowed.
<button onClick={() => setCount(count - 1)}>-</button>
//Not allowed
<button onClick={increment()}>-</button>
//Allowed
<button onClick={increment}>-</button>
Salient points about useState:
useState will accept boolean, variable, number, array and objects as initial values.
useState will re-render the component for every change in state. To avoid this, we can provide an arrow function with initial value inside it, to the useState hook.
const [count, setCount] = useState(() => {return 0})
If you have multiple useState in your react app, then react combines all the states as one to update the state values. This makes setCount as asynchronous, meaning you would not have the latest updated values always. This can be avoided by setting the state with a prevState parameter, like below.
setCount(prevState => prevState + 1)
Updating the object in setState needs destructing, or else you might not have the whole state object.
const[state, setState] = useState({a: 1, b: 2})
setState(prevState => prevState.a + 1) // this will return only {a : 2}
setState(prevState => {...prevState, prevState.a + 1}) // this will return {a: 2, b:2}
useEffect:
Class components have life cycle methods which run for corresponding situations like componentDidMount, componentWillUnmount, componentDidUpdate. These methods take care of any side effects in a React app like fetching a data using API, making API calls when there is a state change and clearing up the components on the initial render or unmounting from DOM tree.
To have the same scenarios handled in the functional based components, we have the hook called useEffect. This hook will take a callback function as its one argument and dependency array as the second argument.
useEffect(() => {console.log('this is useEffect hook')}, [randomArray])
Based on the dependency array, useEffect will run on a render.
No dependency array
useEffect(() => {
//runs on every render of component.
});
Empty dependency array
useEffect(() => {
//runs only on first render of component
}, []);
Dependency array
useEffect(() => {
//runs on the first render and
// on every subsequent change in the dependency array values.
}, [props/state]);
Using the same CounterApp example above, we will add useEffect hook to it and refractor it to log the number of times we clicked.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Here, the useEffect hook will update the title of the document, every time we perform the click event on the button. Count variable have the number of clicks value, and setCount will update the count variable value on every button click.
useEffect is also used to add event listener and remove event listener, or when we subscribe and unsubscribe to an API.
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize)}
}, []);
The above useEffect will run on the first render of react app, and it will add the event listener. When the component is unmounted, the event listener will be removed.
Conclusion:
Most of the job in a React app can be done with these two hooks. These hooks make the functional components behave as a stateful component.
Resources and References:
React Org Docs - Introducing Hooks
UseState and UseEffect Hooks - useState, useEffect
Subscribe to the newsletter for more insightful info on web development and more technical info, tips and tricks.