Context API備忘録

# はじめに
年末年始にReactとTypeScriptの旅に出てきました。
行ったことの備忘録として記載します。
理解が浅い部分もあるため、諸々ご容赦いただけると助かります。

# Context APIとは
Context APIとはなにか、ないとどうなるかを記載します。

## Context APIのない世界
複数間コンポーネントで値の受け渡しをする際、どうしても煩雑になりがちです。
極端な例ですが、`App.tsx`から子コンポーネント、更に孫へと受け渡すにつれてコードがバケツリレーと化します。
![React.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3871802/6f0cc92c-eee0-a637-79d0-501de85bd356.png)

## Context APIのある世界
グローバル(語弊あり)な空間から値を取得、更新することができます。
バケツリレーの必要はなくなりますが、コンポーネントが特定の値に依存してしまうため、再利用性は下がります。

# Context APIの使い方
使い方は以下の通りです。
1. Contextオブジェクト生成
1. Providerの設置
1. Contextの利用

## Contextオブジェクト生成
~~~AppContext.tsx
interface AppContextType {
  users: User[];
  setUsers: React.Dispatch<React.SetStateAction<User[]>>;
}

export const AppContext = createContext<AppContextType | undefined>(undefined);

export const AppContextProvider = ({ children }: { children: ReactNode }) => {
  const [users, setUsers] = useState<User[]>([]);

  return (
    <AppContext.Provider value={{ users, setUsers }}>
      {children}
    </AppContext.Provider>
  )
};
~~~
`AppContext`は`createContext`で生成したContextオブジェクトです。
`AppContextProvider`はProviderの設置で使用します。

## Providerの設置
~~~App.tsx
function App() {
  return (
    <AppContextProvider>
      <CssBaseline />
      <Router>
        <Routes>
          <Route path="/" element={<AppLayout />}>
            <Route index element={<Home />} />
            <Route path="/report" element={<Report />} />
            <Route path="/*" element={<NoMatch />} />
          </Route>
        </Routes>
        </Router>
      </AppContextProvider>
  );
}
~~~
先ほど作成した`AppContextProvider`をContextを作成したいコンポーネントの上位ツリーに設置します。
つまり、今回の場合には、`Home.tsx`や`Report.tsx`で`AppContext`を読み取れますが、`App.tsx`の中では読み取れません。
あくまで`AppContextProvider`配下のコンポーネントでだけ読み取れます。

## Contextの利用
~~~AppContext.tsx
// useAppContextのカスタムフック
export const useAppContext = () => {
  const context = useContext(AppContext);
  if (!context) {
    // AppContextProvider配下のコンポーネントではない場所での呼び出し
    throw new Error("useAppContext must be used within a AppContextProvider");
  }
  return context;
}
~~~
`useContext(AppContext)`で呼び出した際に`undefined`か判定しなければいけません。
毎度実施するのは面倒ですので、カスタムフックとして記載することで呼び出し側でのコードが簡潔になります。
~~~Home.tsx
export default Home = () => {
  const { users } = useAppContext();
  return (
  ...
  ...
  )
}
~~~
`Home.tsx`では`useAppContext()`を使用するだけで`users`が取り出せました。

## 動的な値の取得について
`users`を元に`user.registration_at`でfilterした`selectedMonthRegistrationUsers`を各所で使用したいとします。
その場合、`AppContext.tsx`ではなく、別ファイルにカスタムフックとしてまとめるほうが複雑になりにくくおすすめです。
~~~useSelectedMonthRegistrationUsers.tsx
export default const useSelectedMonthRegistrationUsers = (): User[] => {
  const { users, registration_from, registration_to } = useAppContext();

  const selectedMonthRegistrationUsers = useMemo(() => {
    return users.filter((user) => {
      const registrationDate = new Date(user.registration_at);
      const fromDate = new Date(registration_from);
      const toDate = new Date(registration_to);

      return registrationDate >= fromDate && registrationDate <= toDate;
    });
  }, [users, registration_from, registration_to]);

  return selectedMonthRegistrationUsers;
}
~~~
なお、カスタムフックは`use~`から始めるのが決まりとのことです。
また、`useMemo`を使用することで、第二引数に渡した値(`[users, registration_from, registration_to]`)が変更された時だけ更新されるようになります。

# さいごに
用法用量を守って使わないと、いけない。
銀の弾丸はない。