【React】管理画面を作りながらReactを学ぶ(ChakraUI/コンポーネント化/react rooter v6)

# まえがき
百聞は一見に如かずということで、典型的な管理画面を作りながらReactを学んでいく。

シリーズ形式の記事となります。今回は１つめ。
**1. 【React】管理画面を作りながらReactを学ぶ(ChakraUI/コンポーネント化/react rooter v6)**
[2. 【React】管理画面を作りながらReactを学ぶ(props/ライフサイクル関数/API呼び出し)](https://qiita.com/crml1206/items/fe2f0d465e396c75cb01#account%E7%94%BB%E9%9D%A2%E3%81%A7%E3%83%87%E3%83%BC%E3%82%BF%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B)
[3. 【React】管理画面を作りながらReactを学ぶ(ビルド)](https://qiita.com/crml1206/items/a905e08b3de5226076d6)

# この記事に書くこと
管理画面のレイアウト部分をメインに作りながら、下記について説明していく。
・ChakraUIの基本的な使い方（ヘッダ/サイドメニューの作成）
・Reactコンポーネントの分け方
・ルーティングのやり方(react rooter v6)

最後に完成する管理画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/e6f31540-b53d-b4eb-9e00-2b0cd30fa320.png)
# やっていこう
## create-react-appプロジェクト作成
### プロジェクト作成
・ChakraUIを使うため、templateとして`@chakra-ui/typescript`を指定
```
yarn create react-app chakra-react-practice --template @chakra-ui/typescript
```

https://chakra-ui.com/guides/getting-started/cra-guide

### 起動
```
yarn start
```
初期画面こんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/26c0ec56-c93a-de80-25ce-9e8a6363544d.png)

## ChakraUIについておさらい

プロジェクト作成時点のApp.tsxの中身を見てみよう。
```typescript
import * as React from "react"
import {
  ChakraProvider,
  Box,
  Text,
  Link,
  VStack,
  Code,
  Grid,
  theme,
} from "@chakra-ui/react"
import { ColorModeSwitcher } from "./ColorModeSwitcher"
import { Logo } from "./Logo"

export const App = () => (
  <ChakraProvider theme={theme}>
    <Box textAlign="center" fontSize="xl">
      <Grid minH="100vh" p={3}>
        <ColorModeSwitcher justifySelf="flex-end" />
        <VStack spacing={8}>
          <Logo h="40vmin" pointerEvents="none" />
          <Text>
            Edit <Code fontSize="xl">src/App.tsx</Code> and save to reload.
          </Text>
          <Link
            color="teal.500"
            href="https://chakra-ui.com"
            fontSize="2xl"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn Chakra
          </Link>
        </VStack>
      </Grid>
    </Box>
  </ChakraProvider>
)
```

### ChakraProviderとは？
・ChakraUIが提供するコンポーネントを使うためには、`ChakraProvider`を最も上位の階層で返す必要がある。
・propsとしてデフォルトの`theme`を渡している。カスタマイズしたい場合はここのthemeを変更する。

```typescript
import {ChakraProvider, theme} from "@chakra-ui/react"

export const App = () => (
  <ChakraProvider theme={theme}>
```

## ヘッダ/サイドメニューを作る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/ecfac001-0a04-d97f-9074-3b64a15a9c54.png)

・コンポーネントは分けず、App.tsx内だけでとりあえず書いてみた。
・サイドメニューとして使えそうなChakraUIコンポーネントとして「Drawer」があったが、これは名前の通りボタンをクリックしたら表示される(引き出される/drawされる)ものだったため、Boxコンポーネント、Buttonコンポーネントで実装。

```typescript
import {
  ChakraProvider,
  Box,
  Button,
  Flex,
  Icon,
  Image,
  theme,
} from "@chakra-ui/react";
import {
  MdMessage,
  MdDashboard,
  MdEmail,
  MdAccountBox,
  MdInsertChart,
  MdWbSunny,
  MdRateReview,
} from "react-icons/md";

export const App = () => {
  // SideMenuコンポーネント内に定義する
  const sideMenuItems = [
    {
      name: "Dashboard",
      icon: MdDashboard,
    },
    {
      name: "Account",
      icon: MdAccountBox,
    },
    {
      name: "Mailbox",
      icon: MdEmail,
    },
    {
      name: "Message",
      icon: MdMessage,
    },
    {
      name: "Charts",
      icon: MdInsertChart,
    },
    {
      name: "Weather",
      icon: MdWbSunny,
    },
    {
      name: "RateReview",
      icon: MdRateReview,
    },
  ];

  return (
    <ChakraProvider theme={theme}>
      <Flex w="100vw" h="100wh">
        {/* Headerコンポーネントとして切り出す */}
        <Flex
          as="header"
          position="fixed"
          bg="gray.100"
          top={0}
          width="full"
          height="100px"
          shadow="sm"
          py={4}
          px={8}
        >
          <Image src="/sampleLogo.PNG" />
        </Flex>
        <Box mt="100px">
          <Flex>
            {/* SideMenuコンポーネントとして切り出す */}　　　　　
            <Box w="20vw" m="20px">
              {sideMenuItems.map((item) => (
                <label>
                  <Box mt="10px" ml="10px">
                    <Button variant="ghost">
                      <Icon as={item.icon} w={7} h={7} mr="13px" />
                      {item.name}
                    </Button>
                  </Box>
                </label>
              ))}
            </Box>
            <Box w="70vw">
              {/* サイドメニューアイコンをクリックする度にここが切り替わる */}
            </Box>
          </Flex>
        </Box>
      </Flex>
    </ChakraProvider>
  );
};

```


### as propとは？
・デフォルトで全てのCharkraコンポーネントについている
・コンポーネントが出力するタグをオーバライド可能
・例) Flexコンポーネントはデフォルトでdiv要素として生成されるが、header要素として生成したい場合に`as="header"`と指定する。

## コンポーネント分ける
・ヘッダとサイドメニューをコンポーネント化する
・src/App.tsxに対して、コンポーネント化した「TopHeader.tsx」「SideMenu.tsx」はsrc/componentsフォルダに格納する。
　
　src/
　　・App.tsx
　　・components/
　　　　・TopHeader.tsx
　　　　・SideMenu.tsx

src/App.tsx
```typescript
import { ChakraProvider, Box, Flex, theme } from "@chakra-ui/react";
// componentsフォルダ配下のTopHeader.tsx、SideMenu.tsxをインポート
import { TopHeader } from "./components/TopHeader";
import { SideMenu } from "./components/SideMenu";

export const App = () => {
  return (
    <ChakraProvider theme={theme}>
      <Flex w="100vw" h="100wh">
        <TopHeader />
        <Box mt="100px">
          <Flex>
            <SideMenu />
            <Box w="70vw">
              {/* サイドメニューアイコンをクリックするとここが切り替わる */}
            </Box>
          </Flex>
        </Box>
      </Flex>
    </ChakraProvider>
  );
};
```

src/components/TopHeader.tsx
```typescript
import { Flex, Image } from "@chakra-ui/react";

export const TopHeader = () => (
  <Flex
    as="header"
    position="fixed"
    bg="gray.100"
    top={0}
    width="full"
    height="100px"
    shadow="sm"
    py={4}
    px={8}
  >
    <Image src="/sampleLogo.PNG" />
  </Flex>
);
```
src/components/SideMenu.tsx
```typescript
import { Box, Button, Icon } from "@chakra-ui/react";
import {
  MdMessage,
  MdDashboard,
  MdEmail,
  MdAccountBox,
  MdInsertChart,
  MdWbSunny,
  MdRateReview,
} from "react-icons/md";

export const SideMenu = () => {
  const sideMenuItems = [
    {
      name: "Dashboard",
      icon: MdDashboard,
    },
    {
      name: "Account",
      icon: MdAccountBox,
    },
    {
      name: "Mailbox",
      icon: MdEmail,
    },
    {
      name: "Message",
      icon: MdMessage,
    },
    {
      name: "Charts",
      icon: MdInsertChart,
    },
    {
      name: "Weather",
      icon: MdWbSunny,
    },
    {
      name: "RateReview",
      icon: MdRateReview,
    },
  ];
  return (
    <Box w="20vw" m="20px">
      {sideMenuItems.map((item) => (
        <label>
          <Box mt="10px" ml="10px">
            <Button variant="ghost">
              <Icon as={item.icon} w={7} h={7} mr="13px" />
              {item.name}
            </Button>
          </Box>
        </label>
      ))}
    </Box>
  );
};
```

## サイドメニューアイコンをクリックしたら画面が切り替わるようにする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/ecfac001-0a04-d97f-9074-3b64a15a9c54.png)
サイドメニューアイコンををクリックしたら、共通部分(ヘッダ/サイドメニュー)は変わることなく画面(Dashboard,Account,etc)だけ切り替わるようにしたい。これをReactで実現するには**React Rooter**を利用する必要がある。

### React Rooterをインストール
```
yarn add react-router-dom
```

・今(2022/03/05)のReact Rooterのバージョンは「6」
・バージョンごとに書き方が異なるため、ぐぐると色々でてくるのでバージョンを意識しておく。

https://dev.classmethod.jp/articles/react-router-5to6/

### ルーティング先の画面を用意
ルーティング設定の前に、ルーティング先の画面をroutesフォルダ配下に用意する。

　src/
　　・App.tsx
　　・routes/
　　　　・Dashboard.tsx
　　　　・Account.tsx
　　　　・MailBox.tsx
　　　　・Message.tsx
　　　　・Charts.tsx
　　　　・Weather.tsx
　　　　・RateReview.tsx

src/routes/Dashboard.ts
・他の画面も同じ要領で作成する。
```typescript
export const Dashboard = () => <h1>Dashboard</h1>;
```

### ルーティング設定
ルーティングで切り替えたいのは`{/* サイドメニューアイコンをクリックするとここが切り替わる */}`の部分である。

App.tsx
```typescript
export const App = () => {
  return (
    <ChakraProvider theme={theme}>
      <Flex w="100vw" h="100wh">
        <TopHeader />
        <Box mt="100px">
          <Flex>
            <SideMenu />
            <Box w="70vw">
              {/* サイドメニューアイコンをクリックするとここが切り替わる */}
            </Box>
          </Flex>
        </Box>
      </Flex>
    </ChakraProvider>
  );
};
```
↓ルーティング設定を追加
```typescript
import { ChakraProvider, Box, Flex, theme } from "@chakra-ui/react";
import { TopHeader } from "./components/TopHeader";
import { SideMenu } from "./components/SideMenu";

// ルーティング設定に必要なものをimport
import { BrowserRouter, Routes, Route } from "react-router-dom";

// ルーティング先の画面コンポーネントをimport
import { Account } from "./routes/Account";
import { Charts } from "./routes/Charts";
import { Dashboard } from "./routes/Dashboard";
import { MailBox } from "./routes/MailBox";
import { Message } from "./routes/Message";
import { RateReview } from "./routes/RateReview";
import { Weather } from "./routes/Weather";

export const App = () => {
  return (
    <ChakraProvider theme={theme}>
      <Flex w="100vw" h="100wh">
        <TopHeader />
        <Box mt="100px">
          <Flex>
            // navigate()はこのBrowserRouter内で呼び出す必要がある。
            <BrowserRouter>
              <SideMenu />
              <Box w="70vw">
                <Routes>
                  <Route path="/" element={<Dashboard />} />
                  <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>
  );
};
```

### アイコンクリックしたらルーティングするように修正

src/components/SideMenu.tsx
```typescript
// react rooter v6で画面遷移するために使うやつ
import { useNavigate } from "react-router-dom";

export const SideMenu = () => {
  // natigate関数を取得
  const navigate = useNavigate();
  return (
    <Box w="20vw" m="20px">
      {sideMenuItems.map((item) => (
        <label>
          <Box mt="10px" ml="10px">
            // ボタンをクリックしたらルーティングする
            <Button variant="ghost" onClick={() => navigate(item.path)}>
              <Icon as={item.icon} w={7} h={7} mr="13px" />
              {item.name}
            </Button>
          </Box>
        </label>
      ))}
    </Box>
  );
};
```

### ルーティング出来ることの確認
http://localhost:3000/ にアクセス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/c7a8d706-1d90-ca48-bfee-48267a1d0b05.png)
↓「Account」アイコンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/d3556a6f-b9aa-b142-b1a1-c724efcae570.png)
・URLが http://localhost:3000/account に遷移している

# ChakraUIメモ
## Box
・ただのdiv要素。ChakraUIの色んなStyleを適用できる便利なdiv要素、という認識。
・Box is the most abstract component on top of which all other Chakra UI components are built. By default, it renders a div element.

## Flex
・「display: flex」のBoxコンポーネント
・Flex is Box with display: flex and comes with helpful style shorthand. It renders a div element.

## Icon

・Chakra UI iconsだと物足りない感があったので`react-icons`を利用する

react-icons一覧

https://react-icons.github.io/react-icons/icons?name=md

↓のコードだと「歯車アイコン(MdSettings)」が表示される。
```typescript
// 1. Import
import { Icon } from '@chakra-ui/react'
import { MdSettings } from 'react-icons/md'

// 2. Use the `as` prop
function Example() {
  return <Icon as={MdSettings} />
}
```

## Button
・↑のアイコンと組み合わせる
```typescript
<Button colorScheme="gray" 
        leftIcon={<Icon as={MdAccountBox} w={9} h={9} />}
        variant="ghost"
>
  <Text m="10px">アカウント</Text>
</Button>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/94c9ba46-7a72-e5a2-dd12-a7d70c423651.png)

・colorSchemeに指定可能な色一覧

https://chakra-ui.com/docs/components/form/button#button-props

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/501b702a-bafb-1a9f-4bd5-a37cb2db3949.png)

・variantで枠のデザインを指定

https://chakra-ui.com/docs/components/form/button#button-variants

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/fc0c5d48-d755-3a06-3090-902f388f110c.png)