React: カスタムフック [アウトプット全くしてこなかったのでアウトプットする014]

カスタムフックも雰囲気で知っていたのでアウトプットします。(何番煎じだよ感がありますが......)

> カスタムフックは複数のコンポーネントの中に存在する共通の処理を取り出して作成した関数です。汎用的なカスタムフックを作成することができれば1つのアプリケーション内で再利用するだけではなく複数のアプリケーションでも再利用することが可能になります。

:::note
カスタムフックを作る際のルール
メソッド名はuseを接頭辞にする
カスタムフック内ではデフォルトのフックを利用する
戻り値のパターンはデフォルトのフックに従う
:::

以下は公式のコピペです。

```jsx: FriendStatus.js
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
```

```jsx: FriendListItem.jsx
import React, { useState, useEffect } from 'react';

function FriendListItem(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}
```

この2つのコードには重複している部分があります。
それが以下で、ここではフレンドステータスがオンラインかオフラインかを判別しています。

```jsx: 重複しているコード
const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
```

上記コードはカスタムフックを使用することでスッキリさせることができます。

以下のコードはカスタムフックとしてファイル名はお作法通り`use`から始まるものになっています。
このフックは返り値として対象のユーザーがオンラインかオフラインかだけを教えてくれるフックです。
返り値はuseStateで管理しているisOnlineを返しています。

```jsx: useFriendStatus.jsx
import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
```

カスタムフックを使う側のコードを見てみましょう

```jsx: FriendStatus.jsx
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
```

```jsx: FriendListItem.jsx
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}
```

めちゃめちゃスッキリしたのと、FriendStatusコンポーネントとFriendListItemコンポーネントにロジックを書くことがなくなり、この二つのコンポーネントはあくまでビュー部分を担当とする責務を持たせることができました。

追記: ゴミみたいなカスタムフックを作成した

```tsx: useEmotion.tsx
import { useState, useEffect, useCallback } from "react";

type Mood = "happy" | "sad" | "pien";

type Emotion = "😀" | "😭" | "🥺";

export default function useEmotion(mood: Mood) {
  const [emotion, setEmotion] = useState<Emotion>();

  const getEmotion = useCallback((mood: Mood) => {
    switch (mood) {
      case "happy":
        return "😀";
      case "sad":
        return "😭";
      case "pien":
        return "🥺";
      default:
        return;
    }
  }, []);

  useEffect(() => {
    setEmotion(getEmotion(mood));
  }, [mood]);

  return emotion;
}
```

```tsx: App.tsx
import useEmotion from "./hooks/useEmotion";

export default function App() {
  const emotion = useEmotion("pien");
  return <div className="App">{emotion}</div>;
}

```

https://codesandbox.io/s/bitter-field-1ids8v?file=/src/hooks/useEmotion.tsx:0-565


### 参考

https://zenn.dev/luvmini511/articles/df410f137d1e21

https://reffect.co.jp/react/react-custom-hook

https://dev-k.hatenablog.com/entry/react-custom-hoohs-hatenablog?utm_source=feed