# 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)

   ```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)

   ```jsx
   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)

   ```jsx
   useEffect(() => {
       // ... (fetch logic from step 2)

       return () => {
           controller.abort(); // Abort the fetch
       };
   }, [/* dependencies */]); // Add dependencies if needed
   ```

### **Example Custom Hook (`useBook`)**

JavaScript(jsx)

```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**

{% tabs %}
{% tab title="Before AbortController" %}

```jsx
// 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
```

{% endtab %}

{% tab title="After AbortController" %}

```jsx
// useBooks.js (updated with AbortController)
import { useState, useEffect } from 'react'
//import { fetchBooks } from './api-client.js'
import { fetchBooks } from './api-client-signal.js'

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

  useEffect(() => {
    // Create an Abort Controller
    const controller = new AbortController() // Create an AbortController instance

    const fetchBookData = async () => {
      setLoading(true)
      setError(null)
      try {
        // const bookData = await fetchBooks(query)
        // pass AbortController signal to fetchBooks
        const bookData = await fetchBooks(query, controller.signal) // Pass signal to fetchBooks
        setBooks(bookData)
      } catch (err) {
        //setError(err.message)
        if (err.name !== 'AbortError') {
          setError(err.message)
        }
      } finally {
        setLoading(false)
      }
    }

    if (query) {
      fetchBookData()
    }

// if you refresh the browser, the code below will kick in and abort the requests!
  // return () => {
  //   controller.abort();
  // };

  }, [query])

  return { books, error, loading }
}

export default useBooks

```

{% endtab %}
{% endtabs %}

**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`**

{% tabs %}
{% tab title="Before AbortController" %}

```javascript
// 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
  }
}
```

{% endtab %}

{% tab title="After AbortController" %}

````javascript
// api-client-signal.js - after using AbortController
const BASE_URL = 'https://www.googleapis.com/books/v1'

export const fetchBooks = async (query, signal) => {
  try {
    const response = await fetch(`${BASE_URL}/volumes?q=${query}`, { signal })
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    const data = await response.json()
    return data.items || []
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Request aborted')
    } else {
      throw error
    }
  }
}
```
````

{% endtab %}
{% endtabs %}

***

#### **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**

```jsx
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

<figure><img src="https://3076957159-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgrDGzRxBIgT2hVX8HkFG%2Fuploads%2FpE3CpohGBX3BZjerC7K5%2FGoogle%20Bookshelf%20App%20result%20with%20Thumnail.jpg?alt=media&#x26;token=bce97384-5ab8-405f-b3f7-8f9edf6e2566" alt="" width="375"><figcaption></figcaption></figure>

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