# ログイン機能の作成2

次にログイン成功した際に返却された`id`と`name`を管理するのですが、通常のstateの場合ページが切り替わった際にstateを保持することができません。ページ遷移でもstateを保持する場合は、**global state**として管理します。

<mark style="color:red;">※動画では、recoilを使用していますが、recoilの開発が終了したため、useContextでの実装を紹介します。</mark>

まず、ログインした際の保持する情報の型を`src/types/login.ts`に定義します。

<pre class="language-typescript" data-title="src/types/login.ts◎"><code class="lang-typescript">export type LoginInfoType = {
  email: string;
  password: string;
};

export type LoginReturnType = {
  id: number;
  name: string;
};

<strong>export type LoginUserType = {
</strong><strong>  id: number;
</strong><strong>  name: string;
</strong><strong>};
</strong>
</code></pre>

次に`src/components`配下に`providers`ディレクトリを作成し、`LoginUserProvider.tsx`を作成します。

そして、以下の内容を記述します。

<pre class="language-typescript" data-title="src/providers/LoginUserProvider.tsx◎"><code class="lang-typescript"><strong>import { createContext, ReactNode, useState } from "react";
</strong><strong>import { LoginUserType } from "../types/login";
</strong><strong>
</strong><strong>export type LoginUserContextType = {
</strong><strong>  loginUser: LoginUserType;
</strong><strong>  setLoginUser: (user: LoginUserType) => void;
</strong><strong>};
</strong><strong>
</strong><strong>export const LoginUserContext = createContext&#x3C;LoginUserContextType | undefined>(
</strong><strong>  undefined
</strong><strong>);
</strong><strong>
</strong><strong>export const LoginUserProvider = ({ children }: { children: ReactNode }) => {
</strong><strong>  const [loginUser, setLoginUser] = useState&#x3C;LoginUserType>({
</strong><strong>    id: 0,
</strong><strong>    name: "",
</strong><strong>  });
</strong><strong>
</strong><strong>  return (
</strong><strong>    &#x3C;LoginUserContext.Provider value={{ loginUser, setLoginUser }}>
</strong><strong>      {children}
</strong><strong>    &#x3C;/LoginUserContext.Provider>
</strong><strong>  );
</strong><strong>};
</strong>
</code></pre>

このファイルでは、`createContext`という関数を使用して、**コンテキスト**を作成します。**コンテキスト**とは、コンポーネントツリーの中でデータを直接渡すことなく、様々な階層のコンポーネント間でデータを共有できる機能です。

今回の場合`LoginUserContext`というコンテキストを作成しています。

そして、`LoginUserProvider`というコンポーネントを作成していますが、その中で`LoginUserContext.Provider`に`value`というpropsとして共有したいデータを渡すことで、`LoginUserProvider`にchildrenとして渡したcomponentにて、そのデータを取り扱うことができます。

今回の場合は、`loginUser`と`setLoginUser`を共有するようにしています。

これらのデータを、プロジェクト全体で使用するために、`LoginUserProvider`で大元のコンポーネントを括る必要があります。

<pre class="language-typescript" data-title="src/main.tsx◎"><code class="lang-typescript">import React from "react";
import ReactDOM from "react-dom/client";
import "./styles/index.css";
import "./styles/destyle.css";
import "./styles/output.css";
import { RouterProvider } from "react-router-dom";
import { router } from "./routes";
<strong>import { LoginUserProvider } from "./providers/LoginUserProvider";
</strong>
ReactDOM.createRoot(document.getElementById("root")!).render(
  &#x3C;React.StrictMode>
<strong>    &#x3C;LoginUserProvider>
</strong><strong>      &#x3C;RouterProvider router={router} />
</strong><strong>    &#x3C;/LoginUserProvider>
</strong>  &#x3C;/React.StrictMode>
);


</code></pre>

また、このコンテキストを呼び出すためのファイルを作成します。

`src`配下に`hooks`ディレクトリを作成し、`useLoginUser.ts`を作成しましょう。

その中に、以下のように記述します。

<pre class="language-typescript" data-title="src/hooks/useLoginUser.ts◎"><code class="lang-typescript"><strong>import { useContext } from "react";
</strong><strong>import { LoginUserContext } from "../providers/LoginUserProvider";
</strong><strong>
</strong><strong>export const useLoginUser = () => {
</strong><strong>  const context = useContext(LoginUserContext);
</strong><strong>  if (context === undefined) {
</strong><strong>    throw new Error("useLoginUser must be used within a LoginUserProvider");
</strong><strong>  }
</strong><strong>  return context;
</strong><strong>};
</strong>
</code></pre>

上記ファイルでは、`useContext`という関数を使用して、作成したコンテキストからデータを取得することができます。

こちら、`createContext`で作成したコンテキストは、`undefined`となる可能性があるので、`undefined`の場合のエラーハンドリングを行っています。

そして、このデータを取得する場合は、以下のように呼び出して使用します。

```
  const { loginUser, setLoginUser } = useLoginUser();
```

ではログインした際に、setStateにてユーザー情報を保存するようにしましょう。

<pre class="language-typescript" data-title="pages/LoingPage.tsx◎"><code class="lang-typescript">import { ChangeEvent, FormEvent, useState } from "react";
import { useSetRecoilState } from "recoil";
import { Input } from "../atoms/Input";
import { PrimaryBtn } from "../atoms/PrimaryBtn";
import { LoginInfoType } from "../../types/login";
import { login } from "../../api/login";
<strong>import { useLoginUser } from "../../hooks/useLoginUser";
</strong>
export const LoginPage = () => {
<strong>  const { setLoginUser } = useLoginUser();
</strong>  const [loginInfo, setLoginInfo] = useState&#x3C;LoginInfoType>({
    email: "",
    password: "",
  })
  const [errorMessage, setErrorMessage] = useState("");

  const changeLoginInfo = (event: ChangeEvent&#x3C;HTMLInputElement>) => {
    const { name, value } = event.target
    setLoginInfo({ ...loginInfo, [name]: value })
  }

  const handleLogin = (event: FormEvent&#x3C;HTMLFormElement>) => {
    setErrorMessage("")
    event.preventDefault()
    try {
<strong>      const resUser = login(loginInfo)
</strong><strong>      setLoginUser({ id: resUser.id, name: resUser.name })
</strong>    } catch {
      setErrorMessage("ログインに失敗しました");
    }
  };
...
</code></pre>

次にログインしたらカレンダーページに遷移するようにしましょう。

`pages`配下に`CalendarPage.tsx`を作成しましょう。

その中で`loginUser`を表示してみましょう。

<pre class="language-tsx" data-title="pages/CalendarPage.tsx◎"><code class="lang-tsx"><strong>import { useLoginUser } from "../../hooks/useLoginUser";
</strong><strong>
</strong><strong>export const CalendarPage = () => {
</strong><strong>  const { loginUser } = useLoginUser();
</strong><strong>
</strong><strong>  return (
</strong><strong>    &#x3C;div>
</strong><strong>      &#x3C;p>{loginUser.id}&#x3C;/p>
</strong><strong>      &#x3C;p>{loginUser.name}&#x3C;/p>
</strong><strong>    &#x3C;/div>
</strong><strong>  );
</strong><strong>};
</strong></code></pre>

`routes.ts`の方にてCalendarPageのパスを設定しましょう。

<pre class="language-tsx" data-title="routes.ts◎"><code class="lang-tsx">import { createBrowserRouter } from "react-router-dom";
import { TopPage } from "./components/pages/TopPage";
import { LoginPage } from "./components/pages/LoginPage";
import { NotLoginLayout } from "./components/templates/NotLoginLayouts";
<strong>import { CalendarPage } from "./components/pages/CalendarPage";
</strong>
export const router = createBrowserRouter([
  {
    path: "/",
    element: &#x3C;NotLoginLayout />,
    children: [
      { index: true, element: &#x3C;TopPage /> },
      { path: "/login", element: &#x3C;LoginPage /> },
<strong>      { path: "/calendar", element: &#x3C;CalendarPage />}
</strong>    ],
  },
]);
</code></pre>

そして、ログイン成功時に/calendarに遷移するようにしましょう。

<pre class="language-tsx" data-title="pages/LoginPage.tsx◎"><code class="lang-tsx">import { ChangeEvent, FormEvent, useState } from "react";
import { useSetRecoilState } from "recoil";
<strong>import { useNavigate } from "react-router-dom";
</strong>import { Input } from "../atoms/Input";
import { PrimaryBtn } from "../atoms/PrimaryBtn";
import { LoginInfoType } from "../../types/login";
import { login } from "../../api/login";
import { useLoginUser } from "../../hooks/useLoginUser";

export const LoginPage = () => {
<strong>  const navigate = useNavigate()
</strong>const { setLoginUser } = useLoginUser();
  const [loginInfo, setLoginInfo] = useState&#x3C;LoginInfoType>({
    email: "",
    password: "",
  })
  const [errorMessage, setErrorMessage] = useState("");

  const changeLoginInfo = (event: ChangeEvent&#x3C;HTMLInputElement>) => {
    const { name, value } = event.target
    setLoginInfo({ ...loginInfo, [name]: value })
  }

  const handleLogin = (event: FormEvent&#x3C;HTMLFormElement>) => {
    setErrorMessage("")
    event.preventDefault()
    try {
      const resUser = login(loginInfo)
      setLoginUser({ id: resUser.id, name: resUser.name })
<strong>      navigate("/calendar")
</strong>    } catch {
      setErrorMessage("ログインに失敗しました");
    }
  };
...
</code></pre>

これでログイン成功時に`/calendar`に遷移し、`loginUser`をglobal stateとして管理しているので、`loginUser`が表示されているかと思います。

<figure><img src="https://1869761657-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcUBbYqol4PMzZJggiMqV%2Fuploads%2FKQ2plUsUE516a5xaWaj9%2Fpp.png?alt=media&#x26;token=cbfd8cc4-014c-4de4-97a1-32c9ec02b494" alt=""><figcaption></figcaption></figure>

ここまでの作業をcommitしましょう。

```bash
git add .
```

```bash
git commit
```

commitメッセージは

```
feat: recoilを導入し、ログインユーザーをglobal stateにて管理するようにした
```

としましょう。

ではログインページの作成が完了したので、GitHubにpushしましょう。

```basic
git push origin feature/login-func
```

これでGitHubのリポジトリのページにいき、main <- feature/login-funcのPRを作成しましょう。

PRのタイトルは

```
feat: ログイン機能を作成
```

としましょう。

作成したら自身で`Merge pull request`を押してmergeしましょう。

mergeが完了したらWarpに戻り、mainをpullしましょう。

```bash
git switch main
```

```bash
git pull origin main
```

<mark style="color:red;">※recoilの記述は以下に残しておきます。useContextで実装した場合は、以下を実装しなくて大丈夫です。</mark>

~~`recoil`をインストールしましょう。~~

```bash
npm i recoil
```

~~recoilをプロジェクト全体で使用するために、`RecoilRoot`で大元のコンポーネントを括る必要があります。~~

<pre class="language-tsx" data-title="src/main.tsx◎"><code class="lang-tsx">import React from 'react'
import ReactDOM from 'react-dom/client'
<strong>import { RecoilRoot } from "recoil";
</strong>import { RouterProvider } from "react-router-dom";
import './styles/index.css'
import "./styles/output.css"
import "./styles/destyle.css";
import { router } from './routes';

ReactDOM.createRoot(document.getElementById("root")!).render(
  &#x3C;React.StrictMode>
<strong>    &#x3C;RecoilRoot>
</strong>      &#x3C;RouterProvider router={router} />
<strong>    &#x3C;/RecoilRoot>
</strong>  &#x3C;/React.StrictMode>
);

</code></pre>

~~次にglobal stateを定義するのですが、`src`配下に`store`ディレクトリを作成し、その中に`loginUserState.ts`を作成します。そして以下のように記述することで、`{ id: 0, name: "" }`のstateを定義することができます。~~

{% code title="atoms/loginUserState.ts◎" %}

```typescript
import { atom } from "recoil";

type LoginUserType = {
  id: number;
  name: string;
};

export const loginUserState = atom<LoginUserType>({
  key: "loginUser",
  default: {
    id: 0,
    name: "",
  },
});
```

{% endcode %}

~~このstateを使用する場合は、3通りの方法があり、まずstateのみを使用する場合は、`useRecoilValue`を呼び出し、setStateを使用する場合は、`useSetRecoilState`を呼び出し、state,setStateの両方を使用する場合は、`useRecoilState`を呼び出します。そして、引数に先ほど定義したstate(loginUserState)を渡します。~~

```tsx
// stateのみ
const loginUser = useRecoilValue(loginUserState)
// setStateのみ
const setLoginUser = useSetRecoilState(loginUserState)
// state、setStateの両方
const [loginUser, setLoginUser] = useRecoilState(loginUserState)
```

~~ではログインした際に、setStateにてユーザー情報を保存するようにしましょう。~~

<pre class="language-tsx" data-title="pages/LoingPage.tsx◎"><code class="lang-tsx">import { ChangeEvent, FormEvent, useState } from "react";
<strong>import { useSetRecoilState } from "recoil";
</strong>import { Input } from "../atoms/Input";
import { PrimaryBtn } from "../atoms/PrimaryBtn";
import { LoginInfoType } from "../../types/login";
import { login } from "../../api/login";
<strong>import { loginUserState } from "../../store/loginUserState";
</strong>
export const LoginPage = () => {
<strong>  const setLoginUser = useSetRecoilState(loginUserState)
</strong>  const [loginInfo, setLoginInfo] = useState&#x3C;LoginInfoType>({
    email: "",
    password: "",
  })
  const [errorMessage, setErrorMessage] = useState("");

  const changeLoginInfo = (event: ChangeEvent&#x3C;HTMLInputElement>) => {
    const { name, value } = event.target
    setLoginInfo({ ...loginInfo, [name]: value })
  }

  const handleLogin = (event: FormEvent&#x3C;HTMLFormElement>) => {
    setErrorMessage("")
    event.preventDefault()
    try {
<strong>      const resUser = login(loginInfo)
</strong><strong>      setLoginUser({ id: resUser.id, name: resUser.name })
</strong>    } catch {
      setErrorMessage("ログインに失敗しました");
    }
  };
...
</code></pre>

~~次にログインしたらカレンダーページに遷移するようにしましょう。~~

~~`pages`配下に`CalendarPage.tsx`を作成しましょう。~~

~~その中で`loginUser`を表示してみましょう。~~

<pre class="language-tsx" data-title="pages/CalendarPage.tsx◎"><code class="lang-tsx"><strong>import { useRecoilValue } from "recoil"
</strong><strong>import { loginUserState } from "../../store/loginUserState"
</strong><strong>
</strong><strong>export const CalendarPage = () => {
</strong><strong>  const loginUser = useRecoilValue(loginUserState)
</strong><strong>  return (
</strong><strong>    &#x3C;div>
</strong><strong>      &#x3C;p>{loginUser.id}&#x3C;/p>
</strong><strong>      &#x3C;p>{loginUser.name}&#x3C;/p>
</strong><strong>    &#x3C;/div>
</strong><strong>  );
</strong><strong>}
</strong></code></pre>

~~`routes.ts`の方にてCalendarPageのパスを設定しましょう。~~

<pre class="language-tsx" data-title="routes.ts◎"><code class="lang-tsx">import { createBrowserRouter } from "react-router-dom";
import { TopPage } from "./components/pages/TopPage";
import { LoginPage } from "./components/pages/LoginPage";
import { NotLoginLayout } from "./components/templates/NotLoginLayouts";
<strong>import { CalendarPage } from "./components/pages/CalendarPage";
</strong>
export const router = createBrowserRouter([
  {
    path: "/",
    element: &#x3C;NotLoginLayout />,
    children: [
      { index: true, element: &#x3C;TopPage /> },
      { path: "/login", element: &#x3C;LoginPage /> },
<strong>      { path: "/calendar", element: &#x3C;CalendarPage />}
</strong>    ],
  },
]);
</code></pre>

~~そして、ログイン成功時に/calendarに遷移するようにしましょう。~~

<pre class="language-tsx" data-title="pages/LoginPage.tsx◎"><code class="lang-tsx">import { ChangeEvent, FormEvent, useState } from "react";
import { useSetRecoilState } from "recoil";
<strong>import { useNavigate } from "react-router-dom";
</strong>import { Input } from "../atoms/Input";
import { PrimaryBtn } from "../atoms/PrimaryBtn";
import { LoginInfoType } from "../../types/login";
import { login } from "../../api/login";
import { loginUserState } from "../../store/loginUserState";

export const LoginPage = () => {
<strong>  const navigate = useNavigate()
</strong>  const setLoginUser = useSetRecoilState(loginUserState)
  const [loginInfo, setLoginInfo] = useState&#x3C;LoginInfoType>({
    email: "",
    password: "",
  })
  const [errorMessage, setErrorMessage] = useState("");

  const changeLoginInfo = (event: ChangeEvent&#x3C;HTMLInputElement>) => {
    const { name, value } = event.target
    setLoginInfo({ ...loginInfo, [name]: value })
  }

  const handleLogin = (event: FormEvent&#x3C;HTMLFormElement>) => {
    setErrorMessage("")
    event.preventDefault()
    try {
      const resUser = login(loginInfo)
      setLoginUser({ id: resUser.id, name: resUser.name })
<strong>      navigate("/calendar")
</strong>    } catch {
      setErrorMessage("ログインに失敗しました");
    }
  };
...
</code></pre>

~~これでログイン成功時に`/calendar`に遷移し、`loginUser`をglobal stateとして管理しているので、`loginUser`が表示されているかと思います。~~

<figure><img src="https://1869761657-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcUBbYqol4PMzZJggiMqV%2Fuploads%2FKQ2plUsUE516a5xaWaj9%2Fpp.png?alt=media&#x26;token=cbfd8cc4-014c-4de4-97a1-32c9ec02b494" alt=""><figcaption></figcaption></figure>
