【React】管理画面を作りながらReactを学ぶ(ライフサイクル関数/props/dynamic routing)

# まえがき

シリーズ形式の記事となります。今回は２つめ。
[1. 【React】管理画面を作りながらReactを学ぶ(ChakraUI/コンポーネント化/react rooter v6)](https://qiita.com/crml1206/items/fe8bbb784a28e6d24b80)
**2. 【React】管理画面を作りながらReactを学ぶ(ライフサイクル関数/props/dynamic routing)**
[3. 【React】管理画面を作りながらReactを学ぶ(ビルド)](https://qiita.com/crml1206/items/a905e08b3de5226076d6)

## 前回の記事で作った管理画面
・レイアウト(ヘッダ/サイドメニュー)実装
・ルーティング(サイドメニューアイコンをクリックで画面切替)実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/f92e3f68-e8b4-b9a0-c7e6-8027b1eb2722.png)

今回の記事では「**ライフサイクル関数**」「**props**」を利用して、各画面が表示された時にそれっぽいデータをテーブル形式で表示してみる。


# ライフサイクル
・DOMが生成されてから破棄されるまでの一連の流れのこと
・各タイミングで実行させたい処理を「**ライフサイクル関数**」内に定義する。

　例1) Account画面表示する時に、Account一覧取得APIをコールして取得結果を画面に表示したい。
　例2) stateで管理しているsearchKeywordが更新される度に検索処理をかけ直す。

## 「クラスコンポーネント」のライフサイクル関数
今回は関数コンポーネントで実装しているが、勉強がてらクラスコンポーネントのライフサイクル関数についてもMEMOしておく。

https://iktakahiro.hatenablog.com/entry/2018/05/28/123000

### componentDidMount()
・Component が Mount された後に実行される。
・Ajax でデータフェッチ（初回）
・DOM に対する処理を行う（初回）

### componentDidUpdate()
・Component の props または state が変更されたときに実行される。
・Ajax でデータフェッチ（2回目以降）
・DOM に対する処理を行う（2回目以降）

### componentWillUnmount()
・Component が Unmount されるときに実行される。
・componentDidMount() で行った処理の解除を行うことが多い。

## 「関数コンポーネント」のライフサイクル関数

・componentDidMount()
・componentDidUpdate()
・componentWillUnmount()
は、関数コンポーネントだと`useEffect()`１つで済む。

### useEffect()
構文
```typescript
useEffect(() => {
  /* 第1引数には実行させたい副作用関数を記述*/
  console.log('副作用関数が実行されました！')
},[依存する変数の配列]) // 第2引数には副作用関数の実行タイミングを制御する依存データを記述
```
・第2引数を省略した場合、コンポーネントがレンダリングされるたびに、第1引数で渡した副作用関数が実行される＝componentDidMount()扱い

https://qiita.com/seira/items/e62890f11e91f6b9653f

useEffect()に渡す無名関数はasyncだと怒られるので、そうならないようにthen().catch()等で完結させる

https://qiita.com/daishi/items/4423878a1cd7a0ab69eb

Reactでデータ取得する方法についてまとまっているサイト

https://www.robinwieruch.de/react-fetching-data/#how-to-fetch-data-in-react

# Account画面でデータ表示してみる。
今のAccount画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/b14617c8-d97e-ad53-b25c-1eff2091295f.png)
今のsrc/routes/Account.tsx
```typescript
export const Account = () => <h1>Account</h1>;
```

「Account」という文字だけ表示されている画面を、Account情報一覧が表示される感じにしたい。

やること
・画面表示時/componentDidMount()実行時　に、Account一覧取得APIをコールする。
・取得結果を画面に表示する。

APIサーバ構築するのはしんどいので、今回はaxiosでJSONファイルを返す。
```
yarn add axios
```

public/data/accounts.json を用意
```json
[
  {
    "id": "act_0000001",
    "name": "Anthony",
    "age": 26,
    "gender": "男"
  },
  {
    "id": "act_0000002",
    "name": "Bryant",
    "age": 36,
    "gender": "男"
  },
  {
    "id": "act_0000003",
    "name": "Carmelo",
    "age": 35,
    "gender": "男"
  },
  {
    "id": "act_0000004",
    "name": "Daisy",
    "age": 21,
    "gender": "女"
  }
]
```
src/routes/Account.tsx を修正
```typescript
// axiosをimport
import axios from "axios";
import { useEffect, useState } from "react";
import { Box, Table, Thead, Tbody, Tr, Th, Td } from "@chakra-ui/react";

// Account情報入れ物クラス
class AccountDto {
  constructor(
    public id = "",
    public name = "",
    public age = 0,
    public gender = ""
  ) {}
}

export const Account = () => {
  // アカウント情報格納用のstateを用意
  const [accounts, setAccounts] = useState([]);
  // useEffect()に渡す無名関数はasyncだと怒られるので、そうならないようにthen().catch()等で完結させる
  useEffect(() => {
    // publicフォルダ配下に対してHTTPリクエストを行うため「public/data/account.json」を取得
    axios.get("/data/accounts.json").then((res) => {
      setAccounts(res.data);
    });
  }, []);

  return (
    <div>
      <Box m="10px">アカウント管理画面</Box>
      <Table>
        <Thead>
          <Tr>
            <Th>ID</Th>
            <Th>NAME</Th>
            <Th>AGE</Th>
            <Th>GENDER</Th>
          </Tr>
        </Thead>
        <Tbody>
          {accounts.map((a: AccountDto) => {
            return (
              <Tr>
                <Td>{a.id}</Td>
                <Td>{a.name}</Td>
                <Td>{a.age}</Td>
                <Td>{a.gender}</Td>
              </Tr>
            );
          })}
        </Tbody>
      </Table>
    </div>
  );
};
```


画面こんな感じにできた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/aa9272e5-16d5-cc6b-7eec-931c7ebbc26b.png)

# Account情報テーブルをコンポーネント化する＆props利用
やること
・Account.tsxの`<Table>~~</Table>`の部分をsrc/components/account/AccountTable.tsxとしてコンポーネント化する。
・親コンポーネント(Account.tsx)から子コンポーネント(AccountTable.tsx)へ値(Account一覧情報)を**props経由で**渡す。

## propsとは
・親コンポーネントから子コンポーネントへ値を渡すために使うパイプ的な役割なやつ

### 関数コンポーネントでのpropsの渡し方

#### 渡す方(親コンポーネント)
Account.tsx
```typescript
<AccountTable accounts={accounts} />
```
#### 受け取る方(子コンポーネント)
AccountTable.tsx
```typescript
class AccountDto {
  constructor(
    public id = "",
    public name = "",
    public age = 0,
    public gender = ""
  ) {}
}

// propsとして受け取る型を定義
type Props = {
  accounts: AccountDto[];
};

// 関数の引数経由でpropsを受け取る
export const AccountTable = (props: Props) => {
  return (
    <Table>
      <Thead>
        <Tr>
          <Th>ID</Th>
          <Th>NAME</Th>
          <Th>AGE</Th>
          <Th>GENDER</Th>
        </Tr>
      </Thead>
      <Tbody>
        {props.accounts.map((a: AccountDto) => {
          return (
```

## コンポーネント化＆propsでアカウント情報一覧を渡してみた。
親：src/routes/Account.tsx
```typescript
import axios from "axios";
import { useEffect, useState } from "react";
import { AccountTable } from "../components/account/AccountTable";
import { Box } from "@chakra-ui/react";

export const Account = () => {
  const [accounts, setAccounts] = useState([]);

  useEffect(() => {
    axios.get("/data/accounts.json").then((res) => {
      setAccounts(res.data);
    });
  }, []);

  return (
    <div>
      <Box m="10px">アカウント管理画面</Box>
      <AccountTable accounts={accounts} />
    </div>
  );
};
```
子：src/components/AccountTable.tsx
```typescript
import { Table, Thead, Tbody, Tr, Th, Td } from "@chakra-ui/react";

class AccountDto {
  constructor(
    public id = "",
    public name = "",
    public age = 0,
    public gender = ""
  ) {}
}

// propsとして受け取る型を定義
type Props = {
  accounts: AccountDto[];
};

export const AccountTable = (props: Props) => {
  return (
    <Table>
      <Thead>
        <Tr>
          <Th>ID</Th>
          <Th>NAME</Th>
          <Th>AGE</Th>
          <Th>GENDER</Th>
        </Tr>
      </Thead>
      <Tbody>
        {props.accounts.map((a: AccountDto) => {
          return (
            <Tr>
              <Td>{a.id}</Td>
              <Td>{a.name}</Td>
              <Td>{a.age}</Td>
              <Td>{a.gender}</Td>
            </Tr>
          );
        })}
      </Tbody>
    </Table>
  );
};
```

# アカウント詳細画面を作る
アカウント一覧テーブルの「詳細」ボタンを押すと、詳細画面が開かれるようにする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/457b917f-e5a0-19b3-0b6d-bbcca253e6c5.png)

## dynamic routingの設定
詳細画面を開くときに、パラメータとしてアカウントIDを渡して、そのIDでアカウント情報取得APIをたたく＆詳細画面に結果を表示する。

### ルーティング設定追加
・`<Route path="/account/:id" element={<AccountDetail />} />`を追加
```typescript
export const App = () => {
  return (
    <ChakraProvider theme={theme}>
      <Flex w="100vw" h="100wh">
        <TopHeader />
        <Box mt="100px">
          <Flex>
            <BrowserRouter>
              <SideMenu />
              <Box w="70vw">
                <Routes>
                  <Route path="/" element={<Dashboard />} />
                  <Route path="/account/:id" element={<AccountDetail />} />
                  <Route path="/account" element={<Account />} />
                  <Route path="/charts" element={<Charts />} />
                  <Route path="/dashboard" element={<Dashboard />} />
                  <Route path="/mailBox" element={<MailBox />} />
                  <Route path="/message" element={<Message />} />
                  <Route path="/rateReview" element={<RateReview />} />
                  <Route path="/weather" element={<Weather />} />
                </Routes>
              </Box>
            </BrowserRouter>
          </Flex>
        </Box>
      </Flex>
    </ChakraProvider>
  );
};
```
### アカウント詳細画面の作成
・パラメータに渡したIDを**useParams関数**で取得する。 ※これもreact-router-dom **v6**のもの
```typescript
import axios from "axios";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { Box } from "@chakra-ui/react";

class AccountDto {
  constructor(
    public id = "",
    public name = "",
    public age = 0,
    public gender = ""
  ) {}
}

export const AccountDetail = () => {
  const [account, setAccount] = useState(new AccountDto());
  // パラメータ取得
  const params = useParams();
  useEffect(() => {
    // IDをキーにアカウント情報取得
    axios.get(`/data/account_${params.id}.json`).then((res) => {
      setAccount(res.data);
    });
  /// useEffect関数の第2引数に空配列を指定＝無限ループ防止
  }, []);

  return (
    <Box m="20px">
      <h1>ID：{account.id}</h1>
      <h1>NAME：{account.name}</h1>
      <h1>AGE：{account.age}</h1>
      <h1>GENDER：{account.gender}</h1>
    </Box>
  );
};
```
### アカウント情報テーブルの「詳細」ボタンをクリック→詳細画面に移動するようにする
・`navigate("/account/${a.id}")`で詳細画面に移動

AccountTable.tsx
```typescript
      <Tbody>
        {props.accounts.map((a: AccountDto) => {
          return (
            <Tr>
              <Td>{a.id}</Td>
              <Td>{a.name}</Td>
              <Td>{a.age}</Td>
              <Td>{a.gender}</Td>
              <Td>
                <Button
                  colorScheme="gray"
                  variant="ghost"
                  onClick={() => navigate(`/account/${a.id}`)}
                >
                  <Text m="10px">詳細</Text>
                </Button>
              </Td>
            </Tr>
          );
        })}
      </Tbody>
```

### 動作確認
・ID「act_0000001」をパラメータに渡す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/f9534b79-effa-681b-ae2e-c58ebca4b928.png)
・ID「act_0000002」をパラメータに渡す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/7e8a133c-2410-015a-fd88-1f4475a1d4d4.png)