Lesson 8 - Controlling Network Activities in React with AbortController

1. Controlling Network Activities in React

This section describes how to implement an abort controller in a React application using a custom hook. The goal is to prevent memory leaks and unnecessary network activity by managing HTTP requests. A custom hook, useBook, incorporates the AbortController to cleanly stop fetching data when a component unmounts. This is achieved by passing a signal to the fetch method and using the useEffect hook's cleanup function to call controller.abort(). This ensures that requests are aborted when the component is no longer needed, improving application performance and resource management. The explanation uses the example of fetching book data.

Controlling network activities in React with AbortController is crucial for preventing memory leaks and optimizing performance. Here's an explanation of how it works, its use cases, and implementation:

What is AbortController?

AbortController is a built-in browser API that allows you to abort one or more web requests. It's particularly useful in React applications for managing asynchronous operations like fetching data. Without proper control, if a component unmounts while a fetch request is still in progress, it can lead to:

  • Memory leaks: The component is gone, but the request might still resolve, attempting to update state on an unmounted component, causing errors.

  • Wasted resources: The browser continues to process the request even though the result is no longer needed.

Use Cases in React

  1. Preventing state updates on unmounted components: This is the primary use case. If a component initiates a fetch request and unmounts before the response arrives, attempting to setState will result in a warning or error. AbortController prevents this.

  2. Cancelling ongoing requests when user navigates or performs other actions: For example, if a user starts typing in a search bar that triggers API calls, you might want to abort previous requests when the user types a new character to avoid unnecessary network traffic.

  3. Implementing timeouts: Although AbortController doesn't directly implement timeouts, it can be combined with setTimeout to achieve similar behavior.

How to Implement AbortController in React

The most common and recommended approach is using a custom hook. Here's a breakdown:

  1. Create an AbortController instance: Inside your custom hook (or component), create a new instance of AbortController:

    JavaScript(jsx)

    const controller = new AbortController();
    const signal = controller.signal;
  2. Pass the signal to the fetch request: The signal property of the AbortController is passed as an option to the fetch function:

    JavaScript(isx)

    fetch('/api/books', { signal })
      .then(response => response.json())
      // ... handle the response
      .catch(error => {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted'); // Handle abort specifically
        } else {
          // Handle other errors
        }
      });
  3. Abort the request in the useEffect cleanup function: The crucial step. Use the useEffect hook with a cleanup function to abort the request when the component unmounts or when the dependencies of the useEffect change:

    JavaScript(jsx)

    useEffect(() => {
        // ... (fetch logic from step 2)
    
        return () => {
            controller.abort(); // Abort the fetch
        };
    }, [/* dependencies */]); // Add dependencies if needed

Example Custom Hook (useBook)

JavaScript(jsx)

import { useState, useEffect } from 'react';

function useBook(bookId) {
  const [book, setBook] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    const fetchBook = async () => {
      try {
        const response = await fetch(`/api/books/${bookId}`, { signal });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        setBook(data);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          setError(error);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchBook();

    return () => controller.abort();
  }, [bookId]);

  return { book, loading, error };
}

export default useBook;

Key improvements in this example:

  • Error handling: Includes more robust error handling, including checking for non-OK HTTP responses.

  • finally block: Ensures setLoading(false) is always called, even if there's an error or the request is aborted.

  • Async/await: Uses async/await for cleaner asynchronous code.

By using AbortController in this way, you can effectively manage network requests in your React applications, preventing memory leaks and improving performance.

2. Step-by-Step Explanation of Implementing AbortController in React

Objective

To efficiently manage HTTP requests and prevent memory leaks by using AbortController within a custom hook (useBooks) in a React application.


Step 1. Concept of AbortController

  • What it Does:

    • Allows you to cancel ongoing network requests when they are no longer needed (e.g., component unmounts or requests overlap).

  • How it Works:

    • Create an instance of AbortController.

    • Pass its signal property to the fetch API.

    • Use the abort() method to cancel the request.


Step 2. Updating useBooks Custom Hook

The hook now incorporates AbortController to manage fetch requests.

Code Highlights - useBooks.js

// useBooks.js - before using AbortController

import { useState, useEffect } from 'react'
import { fetchBooks } from './api-client.js'

const useBooks = (query) => {
  const [books, setBooks] = useState([])
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    
    const fetchBookData = async () => {
      setLoading(true)
      setError(null)
      try {
        const bookData = await fetchBooks(query)
        setBooks(bookData)
      } catch (err) {
        //setError(err.message)
        if (err.name !== 'AbortError') {
          setError(err.message)
        }
      } finally {
        setLoading(false)
      }
    }

    if (query) {
      fetchBookData()
    }

  }, [query])

  return { books, error, loading }
}

export default useBooks

Behavior

  • The AbortController cancels any active requests when:

    • The component unmounts.

    • The query changes.


Step 3. Updating fetchBooks in api-client.js

The fetchBooks function needs to accept and use the signal.

Code for api-client.js

// api-client.js - before using AbortController
const BASE_URL = 'https://www.googleapis.com/books/v1'

export const fetchBooks = async (query) => {
  try {
    const response = await fetch(`${BASE_URL}/volumes?q=${query}`)
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    const data = await response.json()
    return data.items || []
  } catch (error) {
    console.error('Error fetching books:', error)
    throw error // Re-throw the error for the caller to handle
  }
}

Step 4. Refactoring AppBookShelf.jsx

Use the updated useBooks hook with the new AbortController functionality.

Key Changes

  1. Loading State:

    • Show a loading message while books are being fetched.

  2. Error Handling:

    • Display errors if the request fails.

Updated Render Logic

javascript코드 복사{loading ? (
  <p className="text-center">Loading...</p>
) : error ? (
  <p className="text-center text-danger">Error: {error}</p>
) : (
  <BookList books={books} query={query} />
)}

Step 5. Benefits of Using AbortController

  1. Prevent Memory Leaks:

    • Cancels unnecessary network requests when the component unmounts or the query changes rapidly.

  2. Improved Performance:

    • Avoids overlapping network requests.

  3. Cleaner Code:

    • Handles asynchronous requests more effectively with the useEffect cleanup function.


Google BookShelf React App v2 : Result with AbortController

This implementation efficiently manages network activities, ensures smooth application performance, and prevents unnecessary resource usage.

Last updated