DEV Community

Jen C.
Jen C.

Posted on

React - Create a custom hook to handle online and offline events by using AbortController and addEventListener

Using AbortController vs removeEventListener

Using AbortController

import { useEffect } from "react"

export function useOnlineStatus(onlineCallback: () => void, offlineCallback: () => void) {
  useEffect(() => {
    const controller = new AbortController()
    window.addEventListener("online", onlineCallback, { signal: controller.signal })
    window.addEventListener("offline", offlineCallback, { signal: controller.signal })

    return () => {
      controller.abort()
    }
  }, [onlineCallback, offlineCallback])
}
Enter fullscreen mode Exit fullscreen mode

vs

Using removeEventListener

import { useEffect } from 'react';

export function useOnlineStatus(
  onlineCallback: () => void,
  offlineCallback: () => void
) {
  useEffect(() => {
    window.addEventListener('online', onlineCallback);
    window.addEventListener('offline', offlineCallback);

    return () => {
      window.removeEventListener('online', onlineCallback);
      window.removeEventListener('offline', offlineCallback);
    };
  }, [onlineCallback, offlineCallback]);
}
Enter fullscreen mode Exit fullscreen mode

Readability

The clean up function ( every re-render and unmounts) of the useEffect can simply call controller.abort()

...

return () => {
    controller.abort()
}

...
Enter fullscreen mode Exit fullscreen mode

Issue: Incorrect online status is rendered

For example,

Use the variable returns by the hook useOnlineStatus in the component App

useOnlineStatus.ts

import { useEffect } from "react"

export function useOnlineStatus(onlineCallback: () => void, offlineCallback: () => void) {
  useEffect(() => {
    const controller = new AbortController()
    window.addEventListener("online", onlineCallback, { signal: controller.signal })
    window.addEventListener("offline", offlineCallback, { signal: controller.signal })

    return () => {
      controller.abort()
    }
  }, [onlineCallback, offlineCallback])

  // return the window online status
  return navigator.onLine;
}
Enter fullscreen mode Exit fullscreen mode

App.tsx

import React, {useCallback} from 'react';
import { useOnlineStatus } from './hook';

export function App(props) {
  const handleOnline = useCallback(() => {
    console.log('online');
  }, []);
  const handleOffline = useCallback(() => {
    console.log('offline');
  }, []);
  const isOnline = useOnlineStatus(handleOnline, handleOffline);
  return (
    <div className='App'>
      <h1>Hello React.</h1>
      <h2>Start editing to see some magic happen!</h2>
      <p>Online status: <span>{isOnline.toString()}</span></p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

However, when switching between "Offline" and "No throttling" on the "Network" tab of Chrome DevTools, the component application does not display the correct online status isOnline.

See that the value of isOnline is false when the connection status is online:

Image description

Root cause

Because only when the component App is rendered, the return value navigator.onLine when calling the hook useOnlineStatus will be assigned to the variable isOnline. However, when the online state changes, the component App will not re-render, which will show the old state of isOnline instead of the current navigator.onLine value in the window.

Solution

Update useOnlineStatus hook to return state variable instead of navigator.onLine.

When the online event is delivered to the target (window), the callback functions onlineCallback and set are called to perform tasks and update the state variables respectively; when the offline event is delivered to the target (window), the working principle is similar.

Declare the state varialbe isOnline

const [isOnline, setIsOnline] = useState(true);
Enter fullscreen mode Exit fullscreen mode

Full codes

import { useEffect, useState, useCallback } from 'react';

export function useOnlineStatus(
  onlineCallback: () => void,
  offlineCallback: () => void
) {
  const [isOnline, setIsOnline] = useState(true);
  const handleOnline = useCallback(() => {
    onlineCallback();
    setIsOnline(true);
  }, []);
  const handleOffline = useCallback(() => {
    offlineCallback();
    setIsOnline(false);
  }, []);

  useEffect(() => {
    const controller = new AbortController();
    window.addEventListener('online', handleOnline, {
      signal: controller.signal,
    });
    window.addEventListener('offline', handleOffline, {
      signal: controller.signal,
    });

    return () => {
      controller.abort();
    };
  }, [onlineCallback, offlineCallback]);
  return isOnline;
}
Enter fullscreen mode Exit fullscreen mode

In this way, when the online/offline status changes, the component App using the useOnlineStatus hook will re-render and display the correct online status.

Top comments (0)