DEV Community

Cover image for Advanced React Hooks: Deep Dive into useEffect Hook
Ryan Thelin for Educative

Posted on • Originally published at educative.io

Advanced React Hooks: Deep Dive into useEffect Hook

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.

Here’s what we’ll cover today:

Become a modern React expert

Master the use and creation of React Hooks with hands-on practice.

A Deep Dive into React Hooks

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.

Alt Text

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.

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>
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

As you can see, it's easy to convert apps to use Hooks and doing so 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]);

Enter fullscreen mode Exit fullscreen mode

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.

Keep learning about React

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.

A Deep Dive into React Hooks

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;
Enter fullscreen mode Exit fullscreen mode

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! <<
Enter fullscreen mode Exit fullscreen mode

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();

}, []);
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

Alt Text

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;
Enter fullscreen mode Exit fullscreen mode

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.

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!

Keep learning about React

Top comments (1)

Collapse
 
blessinghirwa profile image
Blessing Hirwa

Thanks for the article. In addition to what you said about fetching data when the component loads in useEffect, I would recommend anyone who wants to do it to also check Eslint exhaustive-deps rule since it will cause errors most of the times. So make sure you check it out.