📘

Reactでオフライン通知を出したい! with ChatGPT

2023/04/09に公開

何?

  • オフラインになったらその旨を知らせるUIコンポーネントを作成したい
  • せっかくなのでChatGPTと相談して作ってみたい(←こっちメインかも)

TL;DR

  • ChatGPTをコードの叩きに使ってそれをリファクタリングするの良さげ
  • できたコード
    タイトル
    import { useToast } from '@chakra-ui/react';
    import { useCallback, useEffect, useState } from 'react';
    
    type UseNetworkStatus = () => { isOnline: boolean }
    
    type OfflineNotificationProps = {
      useNetworkStatus: UseNetworkStatus
    }
    
    export const OfflineNotification: FC<OfflineNotificationProps> = (props) => {
      const { useNetworkStatus = useNetworkStatus_ } = props;
      const { isOnline } = useNetworkStatus();
      const toast = useToast();
    
      useEffect(() => {
        if (!isOnline) {
          toast({
    	title: 'ネットワークがオフラインになりました',
    	status: 'error',
    	isClosable: true,
          });
        }
      }, [isOnline, toast]);
    
      return (
        <div>
          {isOnline ? (
    	<p>オンラインです</p>
          ) : (
    	<p>オフラインです</p>
          )}
        </div>
      );
    }
    
    const useNetworkStatus_ = () => {
      const [isOnline, setIsOnline] = useState(navigator.onLine);
    
      const handleOnline = useCallback(() => setIsOnline(true), []);
      const handleOffline = useCallback(() => setIsOnline(false), []);
    
      useEffect(() => {
        window.addEventListener('online', handleOnline);
        window.addEventListener('offline', handleOffline);
        return () => {
          window.removeEventListener('online', handleOnline);
          window.removeEventListener('offline', handleOffline);
        };
      }, []);
    
      return {
        isOnline,
      };
    }
    

どうやる?

ChatGPTに相談してみる

質問

あなたはReactを扱うフロントエンドエンジニアです。uiライブラリとしてChakra UIを扱います。ネットワークがオフラインになった時に、トースト通知を出したい。この時のReactのコードサンプルを出してください。

回答

以下はReactとChakra UIを使用して、ネットワークがオフラインになった場合にトースト通知を表示する方法の例です。

import { useState, useEffect } from 'react';
import { useToast } from '@chakra-ui/react';

function App() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  const toast = useToast();

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => {
      setIsOnline(false);
      toast({
        title: 'ネットワークがオフラインになりました',
        status: 'error',
        isClosable: true,
      });
    };

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, [toast]);

  return (
    <div>
      {isOnline ? (
        <p>オンラインです</p>
      ) : (
        <p>オフラインです</p>
      )}
    </div>
  );
}

export default App;

このコードでは、 useState
フックを使用して、現在のオンライン/オフラインの状態を管理します。 useEffect
フックを使用して、ウィンドウの online
/offline
イベントを監視し、状態を更新してトースト通知を表示します。 useToast
フックを使用してChakra UIのトーストを表示します。最後に、コンポーネント内で状態を表示するために条件付きレンダリングを使用しています。

おおそれっぽい。動く。

もう少し整えてみる。

質問

前述のコードをリファクタリングしたい。オフラインかオンラインかのネットワーク状況を検知するカスタムフックと、それを利用してトーストでオフライン通知を行うUIコンポーネントに分けてください。

回答

以下は、前述のコードをリファクタリングして、カスタムフックとUIコンポーネントに分けた例です。

// useNetworkStatus.js
import { useState, useEffect } from 'react';

function useNetworkStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
}

export default useNetworkStatus;
// OfflineNotification.js
import { useToast } from '@chakra-ui/react';
import useNetworkStatus from './useNetworkStatus';

function OfflineNotification() {
  const isOnline = useNetworkStatus();
  const toast = useToast();

  useEffect(() => {
    if (!isOnline) {
      toast({
        title: 'ネットワークがオフラインになりました',
        status: 'error',
        isClosable: true,
      });
    }
  }, [isOnline, toast]);

  return null;
}

export default OfflineNotification;

カスタムフック useNetworkStatus は、現在のオンライン/オフラインの状態を監視し、それを返します。UIコンポーネント OfflineNotification は、この状態を使用して、トースト通知を表示します。状態が変化するたびに **useEffect**フックが呼び出され、オフラインの場合にトースト通知を表示します。 return null は、UIコンポーネントが何も描画しないことを意味します。

これらのコンポーネントを使用するには、以下のようにアプリケーションを構成できます。

import OfflineNotification from './OfflineNotification';

function App() {
  return (
    <div>
      <OfflineNotification />
      <p>ネットワークがオンラインです</p>
    </div>
  );
}

export default App;

このコードでは、 OfflineNotification
コンポーネントを App
コンポーネントに追加しています。 OfflineNotification
は、ネットワークがオフラインになった場合にトースト通知を表示するために使用されます。状態はカスタムフック useNetworkStatus
で管理されます。最後に、状態が変更されるたびに OfflineNotification
がレンダリングされます。

コンポーネントとかカスタムフックの名前いい感じに決めてくれるの地味に嬉しい。

リファクタリングする

このままでも動作はしますしまあいいのですが、もうちょっとstorybookやテストを書きやすい設計にしたいと思いました。

  • 現状だとOfflineNotificationのstorybookでオフライン状態のものを書こうと思うと、navigatorをモックするしかなさそう?例えばspyOnで。
  • つまり、UIコンポーネントがオンライン判定にnavigatorの処理を使用することを知っていなければいけない。もしオンライン判定処理にnavigatorを使わなくなったらstorybookが壊れる。
  • UIコンポーネントがオンライン判定処理の具体を知っている必要はないはず。

カスタムフックを注入する

import { useToast } from '@chakra-ui/react';
import { useCallback, useEffect, useState } from 'react';

type UseNetworkStatus = () => { isOnline: boolean }

type OfflineNotificationProps = {
  useNetworkStatus: UseNetworkStatus
}

export const OfflineNotification: FC<OfflineNotificationProps> = (props) => {
  const { useNetworkStatus = useNetworkStatus_ } = props;
  const { isOnline } = useNetworkStatus();
  const toast = useToast();

  useEffect(() => {
    if (!isOnline) {
      toast({
        title: 'ネットワークがオフラインになりました',
        status: 'error',
        isClosable: true,
      });
    }
  }, [isOnline, toast]);

  return (
    <div>
      {isOnline ? (
        <p>オンラインです</p>
      ) : (
        <p>オフラインです</p>
      )}
    </div>
  );
}

const useNetworkStatus_ = () => {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  const handleOnline = useCallback(() => setIsOnline(true), []);
  const handleOffline = useCallback(() => setIsOnline(false), []);

  useEffect(() => {
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return {
    isOnline,
  };
}

こうすることで、storybookではUIコンポーネントに任意のモックなカスタムフックを渡して、UI作成に集中できる。

storybookは例えばこうなる

import { ComponentMeta, ComponentStoryObj } from "@storybook/react"
import React, { ComponentProps } from "react"
import { OfflineNotification } from "./OfflineNotification"

type Meta = ComponentMeta<typeof OfflineNotification>
type Story = ComponentStoryObj<typeof OfflineNotification>

const componentMeta: Meta = {
  component: OfflineNotification
}

export default componentMeta

const Default: Story = {
  render: () => {
    return <OfflineNotification useNetworkStatus={() => ({ isOnline: false })} />
  }
}

export const Offline: Story = Default

export const Online: Story = {
  ...Default,
  render: () => {
    return <OfflineNotification useNetworkStatus={() => ({ isOnline: true })} />
  }
}
storybookイメージ

オンライン

オフライン

まとめ

  • ChatGPTを叩きに使うのいいなと思った
    • リファクタリングももっとうまく言葉使えば自分でやる必要なかったのかも
    • 情報古かったりするので処理が本当に合ってるかは調べる必要はありそう

参考

Discussion