🥅

Reactでネットワークのオフライン/オンラインを検知するカスタムhooks

2023/09/13に公開

記事にするほどの内容ではないですが作業ログ的に残しておきます。

やりたいこと

下記のハンドリングができるReact hooksを作りたいです。

  • ブラウザでネットワークがオフラインになったときにエラー表示を出したい
  • オンラインになったらエラー表示を消したい

オフライン/オンラインイベント

オフライン/オンラインになったときに発火するイベントがありましたので、これを使います。
https://developer.mozilla.org/ja/docs/Web/API/Window/offline_event
https://developer.mozilla.org/ja/docs/Web/API/Navigator/onLine

また、オンラインかどうかの状態を返すプロパティがあったのでこれも使います。
https://developer.mozilla.org/ja/docs/Web/API/Navigator/onLine

コード

useStateを使って再レンダリングさせるようにします。

import { useEffect, useState } from 'react'

const useOffline = () => {
  const [isOffline, setIsOffline] = useState(!window.navigator.onLine)
  // add event listener
  const onOffline = () => {
    setIsOffline(true)
  }
  const onOnline = () => {
    setIsOffline(false)
  }

  useEffect(() => {
    window.addEventListener('offline', onOffline)
    window.addEventListener('online', onOnline)

    return () => {
      // remove event listener
      window.removeEventListener('offline', onOffline)
      window.removeEventListener('online', onOnline)
    }
  }, [])

  return {
    isOffline,
  }
}

export default useOffline

ついでにテストコード

Jestでのテストコードも書いたので載せておきます。

import { act, renderHook } from '@testing-library/react'

import useOffline from './useOffline'

describe('useOfflineのテスト', () => {
  // イベントを発火させるためのヘルパー関数です
  const fireOnlineOfflineEvent = (type) => {
    const event = new Event(type)
    window.dispatchEvent(event)
  }

  it('オフライン/オンラインになったとき値を返す', () => {
    const { result, rerender } = renderHook(() => useOffline())

    expect(result.current.isOffline).toBe(false)

    // offline イベントをシミュレート
    act(() => {
      fireOnlineOfflineEvent('offline')
      rerender()
    })
    expect(result.current.isOffline).toBe(true)

    // online イベントをシミュレート
    act(() => {
      fireOnlineOfflineEvent('online')
      rerender()
    })
    expect(result.current.isOffline).toBe(false)
  })

  it('アンマウントされるときにクリーン処理が走る', () => {
    const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener')
    const { unmount } = renderHook(() => useOffline())

    unmount()

    expect(removeEventListenerSpy).toHaveBeenCalledWith(
      'offline',
      expect.any(Function)
    )
    expect(removeEventListenerSpy).toHaveBeenCalledWith(
      'online',
      expect.any(Function)
    )
  })
})

Discussion