The most commonly used React Hooks are as follows: useState for managing local state useEffect for side effects like data fetching useContext for accessing context without prop-drilling
With the release of React 16.8 in 2019, React Hooks have finally become available to use in our production applications. Hooks allow React developers to make functional components stateful and avoid Class Components.
UseEffect
is one of the most popular Hooks that allows you to create conditional changes that reference the program state within a functional component.
Today, we’ll take a deeper look into one of the big 3 built-in React Hooks, useEffect
. By the end, you’ll know how and when to implement this Hook to create reactive programs and understand why it’s so commonly used by React developers.
Become a modern React expert
Master the use and creation of React Hooks with hands-on practice.
React has Functional Components that do not hold an internal state and Class Components that add stateful logic to the program and allow you to use lifecycle methods.
Many developers opposed this approach, as Class Components require ES6 classes to maintain internal states.
React Hooks offer an alternative.
React Hooks are functions that allow you to hook into React state and lifecycle features from function components. This allows you to use React without classes, which are widely disliked due to their reliance on JavaScript this
calls. The best part is, Hooks are opt-in and work with existing code.
There are several built-in Hooks, like useEffect
or useState
, that reference common internal states. You can also create custom Hooks that reference states of your choice.
The most popular built-in Hooks are:
useState
: Returns a stateful value and a function to edit it. Think of this as the Hook equivalent of this.state
and this.setState
in Class Components.
useEffect
: Perform side effects from function components. These are queued for after a rerender to allow for limited iterative behavior in React.
useContext
: Accepts a context object and returns current context value. Triggers a re-render next time the nearest MyContext.Provider
updates.
Here are some advantages of React Hooks:
Better code composition: Hooks allow lifecycle methods to be written in a linear, render flowing order rather than splitting them among relevant Class Components.
Reuse states and components: Hooks make it easier to share stateful logic between different components. You use the same Hook to call states throughout a program rather than just within the same Class.
Better testing: Hooks consolidate stateful logic so it’s all defined in a relevant Hook and is, therefore, easier to test.
Performance: When optimized, React Hooks are the fastest form of functional component.
Hooks are designed to be capable of everything Classes can do and more. Let’s see how we can update some old React code to use Hooks instead.
Here’s our old React code without Hooks:
class App extends Component {constructor(props) {super(props);this.state = {message: ''};}componentDidMount() {this.loadMessage();}loadMessage = async () => {try {const response = await axios.get('https://json.versant.digital/.netlify/functions/fake-api/message');this.setState({ message: response.data });} catch (e) {this.setState({ message: e.message });}};render() {return <h1>{this.state.message}</h1>}}
This code uses the componentDidMount
method and this.setState
to reference and manipulate the message status. These features can be replaced by the useEffect
and useState
Hooks.
To convert the code, we’ll:
useState
Hook to manage the message statecomponentDidMount
method with the useEffect
HookuseState
hookHere’s what the same React app looks like using Hooks:
import React, { useEffect, useState } from 'react';import axios from 'axios';const INITIAL_MESSAGE = '';const App = () => {const [message, setMessage] = useState(INITIAL_MESSAGE);useEffect(() => {loadMessage();}, []);const loadMessage = async () => {try {const response = await axios.get('https://json.versant.digital/.netlify/functions/fake-api/message');setMessage(response.data);} catch (e) {setMessage(e.message);}};return <h1>{message}</h1>;};export default App;
As you can see, it’s easy to convert apps to use Hooks and doing so result in more readable code!
useEffect
is one of the most popular Hooks because it allows you to perform side effects in function components. Let’s take a deeper look at the useEffect
Hook to understand how that works.
The
useEffect
Hook lets you run additional code after React has already updated the DOM.
Think of the useEffect
Hook as a partial replacement for React lifecycle events. The useEffect
Hook can replicate the behavior of componentDidMount
, componentDidUpdate
and componentWillUnmount
methods.
In other words, you can respond to changes in any component that contains the useEffect
Hook.
The useEffect
Hook takes two arguments:
useEffect(() => {
// some code
}, [someProp, someState]);
The first argument is a callback function that by default runs after every render.
The second argument is an optional Dependency array that tells the Hook to only callback if there is a change in a target state. The Hook compares the previous and current state value of each dependency.
If the two values don’t match, the Hook uses the first argument callback.
Dependency arrays override the default callback behavior and ensure the Hook ignores everything else in the component scope.
Some common use cases of useEffect
are:
In each case above, useEffect
is used in place of a lifecycle method.
Prepare for a front-end development job by mastering React Hooks. Educative’s text-based courses give you the hands-on practice you’ll need in interviews and on the job.
It’s important to use Dependency Arrays correctly to optimize your useEffect
Hook. One important use of these Hooks is to prevent unnecessary re-renders even when nothing changes.
The code below prints a fetched message to the page but doesn’t use a dependency array.
import React, { useEffect, useState } from 'react';const INITIAL_STATE = '';const App = () => {const [message, setMessage] = useState(INITIAL_STATE);useEffect(() => {loadMessage();});const loadMessage = () => {console.log('>> Loading message <<');try {fetch('https://json.versant.digital/.netlify/functions/fake-api/message').then(res => res.json()).then(message => {setMessage(message);});} catch (e) {}};console.log(`>> Current message is: ${message || 'EMPTY'} <<`);return <h1>{message}</h1>;};export default App;
This seems to be fine, but if when we open the browser console, we can see that the >> Loading Message <<
was rerun multiple times.
>> Current message is: EMPTY <<
>> Loading message <<
>> Current message is: Master React Hooks! <<
>> Loading message <<
>> Current message is: Master React Hooks! <<
Since the message did not change, we should optimize this to only load and fetch the message once. The secret is to add an empty dependency array. We simply replace lines 8-10 with:
useEffect(() => {
loadMessage();
}, []);
By default, the useEffect
Hook runs after each re-render. With a dependency array, it runs once then runs again whenever the passed dependency is changed. An empty array provides no condition where the Hook will run again and therefore ensures that it fetches the message on the first render only.
We can also use populated dependency arrays to make responsive apps.
Imagine we have a React app that allows users to set a nickname using an input field. After the nickname is set, it fetches a personalized greeting message from an external API.
import React, { useEffect, useState } from 'react';const App = () => {const [message, setMessage] = useState('');const [name, setName] = useState('');const [isTyping, setIsTyping] = useState(false);useEffect(() => {// We don't want to fetch message when user is typing// Skip effect when isTyping is trueif (isTyping) {return;}loadMessage(name);}, [name, isTyping]);const loadMessage = nickName => {try {fetch(`https://json.versant.digital/.netlify/functions/fake-api/message/name/${nickName}`).then(res => res.json()).then(message => {setMessage(message);});} catch (e) {}};const handleNameFormSubmit = event => {event.preventDefault();setIsTyping(false);};return (<div className="App"><form onSubmit={handleNameFormSubmit}><inputvalue={name}onChange={event => {setIsTyping(true);setName(event.target.value);}}/><button>Set nickname</button></form><h1>{message}</h1></div>);};export default App;
On lines 8-15, we see that our dependency array contains name
and isTyping
. The useEffect
runs every time there is a change in either of these states. However, you do not want to load the message until the user enters the form or clicks on the “Set nickname” button.
This is achieved with the help of the isTyping
state. If isTyping
is set, we return from the useEffect
function and do not run it (lines 11-13).
When the user finally submits the form, reset isTyping
to false
. The Hook detects the change in the isTyping
state and will run again. It now bypasses the if
statement and this time will call the loadMessage
function to initiate a fetch request.
As you can see, React Hooks are a powerful tool that allows you to bypass many of the frustrating elements of older React syntax. Some next steps from here are to explore other types of Hooks like useContext
or even creating your own custom Hooks.
To help you continue to advanced React Hook implementations, Educative has created A Deep Dive into React Hooks. This course explores every built-in Hook to show you when and how to use each in your own projects. You’ll also learn how to create and optimize custom Hooks.
By the end, you’ll have intimate knowledge of every React Hook and modern functional React as a whole.
Happy learning!
Free Resources