Advanced React Hooks: Deep Dive into useEffect Hook

The Educative Team
codeburst
Published in
7 min readJan 6, 2021

--

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 allowing 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.

Here’s what we’ll cover today:

  • What are React Hooks?
  • What is the useEffect Hook?
  • Using Dependencies Array with useEffect Hook
  • Run useEffect Function with Change in State or Props
  • What to learn next

What are React Hooks?

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.

Composition of Old vs. New React apps

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 re-render 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 components.

Comparing Class implementation and Hook implementation

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:

  • Use the useState Hook to manage the message state
  • Replace componentDidMount method with the useEffect Hook
  • Set a message state using the function provided by useState hook

Here’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 will result in more readable code!

What is the useEffect Hook?

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.

Syntax

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.

Use cases

Some common use cases of useEffect are:

  • Add an event listener for a button
  • Data fetching from API when component mounts
  • Perform an action when state or props change
  • Clean up event listeners when the component unmounts

In each case above, useEffect is used in place of a lifecycle method.

Using Dependencies Array with useEffect Hook

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.

Run useEffect Function with Change in State or Props

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 true
if (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}>
<input
value={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.

You just created a componentDidUpdate method using Hooks!

What to learn next

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.

Happy learning!

--

--

Master in-demand coding skills with Educative’s hands-on courses & tutorials.