【React】思考を整理して簡単なトーク風アプリをつくってみた

# はじめに
自己学習の一環で簡単なトーク風アプリを作りました。
思考が整理されていると人に教える際に迷わなかったり、同じようなアプリをつくる際に再現性が高くなると思うので、今回は思考の整理を意識して実装してみました。

# 各画面について
/top TOP画面
/talk トーク画面
/talk/message メッセージ作成画面

この記事ではTOP画面の実装のみ触れます

TOP画面(Top.tsx)
・推しを選択できる(選択するにはアイコンをクリックする)
・選択したアイコンは「推し」エリアに表示される
・未選択のアイコンは「かわいいやつら」エリアに表示される

トーク画面(割愛)
・推しからのメッセージを確認できる
・推しに送信したメッセージを確認できる

メッセージ作成画面(割愛)
・推しへのメッセージを作成できる

# 実装にあたっての思考プロセス
①画面に表示させたい要素を整理する
- 今回はエリア名(「推し」「かわいいやつら」)と推しのアイコン

②画面で必要な動きと、動きの前後で画面がどうなっているかを整理する。今回は以下の通り。
- アイコンをクリックできる
- モーダルを表示できる
- アイコンが移動する

前の状態
- アイコンがクリックできること
- クリックするとモーダルが表示されること
- モーダルで「スタート」をクリックすると、アイコン「推し」エリアに移動すること

後の状態
- アイコンが「推し」エリアに移動していること
- アイコンがクリックできること
- アイコンをクリックするとトーク画面に遷移すること


以上を踏まえて実装していきます
# ①画面に表示させたい要素を整理する
以下のようにエリア名とアイコンを配置します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/a8160575-291e-f3d4-dc00-57aa3bac9292.png)



```tsx
import React from 'react';
import './App.css';
import { Box, Divider, Text } from '@chakra-ui/react';

function App() {
  return (
    <>
      <Text color="#80BFFF" textAlign="center">推し</Text>
      <Divider />
      <Text color="#80BFFF" textAlign="center">かわいいやつら</Text>
      <Box display="flex" justifyContent="center" alignItems="center">
        <Box
          bg="#95BFEA"
          width="100px"
          height="100px"
          borderRadius="50%"
          overflow="hidden"
          cursor="pointer"
        >
          <img 
            src="/assets/cat.jpg" 
            alt="cat" 
            style={{
              objectFit: 'cover',
              width: '100%',
              height: '100%',
            }}
          />
        </Box>
      </Box>
    </>
   )}
```

# ②画面で必要な動きと、動きの前後で画面がどうなっているかを整理する
- アイコンがクリックできること
onClickをアイコンにつけます

- クリックするとモーダルが表示されること
モーダルはChakra UIを使用することにします

- モーダルで「スタート」をクリックすると、アイコン「推し」エリアに移動すること
アイコンを移動させる条件にstate(isIconMoved)の値を使用することにします。
isIconMovedがfalseであればアイコンは「かわいいやつら」エリアに移動、
trueであれば「推し」エリアに移動するようにします

また、「スタート」をクリックした後はアイコンを「推し」エリアに移動させるので、
isIconMovedをtrueにする必要があります

```diff_typescript
import React from 'react';
import { Box, Divider, Text } from '@chakra-ui/react';
一部省略

function Top() {
+  const { isOpen, onOpen, onClose } = useDisclosure()
+  const [isIconMoved, setIconMoved] = useState(false)
+  
+  const navigate = useNavigate()
+
+  const handleStartClick = () => {
+    setIconMoved(true)
+    onClose()
+  };
+
+  const handleIconClick = () => {
+    if (!isIconMoved) {
+      onOpen();
+    } else {
+      navigate("/talk");
+      window.scrollTo({
+        top: 0,
+        behavior: "smooth",
+      });
+    }
+  };


  return (
    <>
      <Text color="#80BFFF" textAlign="center">推し</Text>
      <Divider />
      <Text color="#80BFFF" textAlign="center">かわいいやつら</Text>
      <Box display="flex" justifyContent="center" alignItems="center">
        <Box
          bg="#95BFEA"
          width="100px"
          height="100px"
          borderRadius="50%"
          overflow="hidden"
          cursor="pointer"
+         onClick={handleIconClick}
        >
          <img 
            src="/assets/cat.jpg" 
            alt="cat" 
            style={{
              objectFit: 'cover',
              width: '100%',
              height: '100%',
            }}
          />
        </Box>
      </Box>
+     <Modal isOpen={isOpen} onClose={onClose}>
+       <ModalOverlay />
+       <ModalContent h="200px">
+         <ModalHeader textAlign="center">
+           モーダルだよ
+         </ModalHeader>
+         <ModalBody>
+           <Box>
+             <Text fontSize="lg" textAlign="center">
+               推し とのトークを始めますか？
+             </Text>
+           </Box>
+           <Spacer />
+           <Divider mt="30px" />
+           <Box display="flex" justifyContent="center">
+             <Button colorScheme="gray" mx="2" onClick={onClose}>
+               キャンセル
+             </Button>
+             <Button bg="#BF80FF" mx="2" onClick={handleStartClick}>
+               <Text color="#FFFFFF">スタート</Text>
+             </Button>
+           </Box>
+         </ModalBody>
+       </ModalContent>
+     </Modal>
    </>
   )}
```

ここまでで、アイコンをクリックすると以下のモーダルが表示されるようになっていると思います

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/780c319d-57f2-9e1b-903c-a8d009704a6d.png)



あとはisIconMovedの値によってアイコンを「推し」エリア、「かわいいやつら」エリアに移動させる実装を加えるとこのようにアイコンが移動してくれます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/94bbc2bd-e13c-72d6-6622-021d151d3bec.png)

最終的なTop.tsxはこちらです

```tsx
import React, { useState } from 'react';
import { Box, Button, Divider, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, Spacer, Text, useDisclosure } from '@chakra-ui/react';
import { useNavigate } from 'react-router'

function Top() {
  const { isOpen, onOpen, onClose } = useDisclosure()
  const [isIconMoved, setIconMoved] = useState(false)
  
  const handleStartClick = () => {
    setIconMoved(true)
    onClose()
  };

  const handleIconClick = () => {
    if (!isIconMoved) {
      onOpen();
    } else {
      navigate("/talk");
      window.scrollTo({
        top: 0,
        behavior: "smooth",
      });
    }
  };

  return (
    <>
      <Box>
        <Text color="#80BFFF" textAlign="center">推し</Text>
        {isIconMoved ? (
          <>
            <Box ml="auto" mr="auto" textAlign="center">
              <Box
                bg="#95BFEA"
                width="100px"
                height="100px"
                borderRadius="50%"
                overflow="hidden"
                onClick={handleIconClick}
                cursor="pointer"
              >
                <img 
                  src="/assets/cat.jpg" 
                  alt="cat" 
                  style={{
                    objectFit: 'cover',
                    width: '100%',
                    height: '100%',
                  }}
                />
              </Box>
              <Text color="#80BFFF" textAlign="center">かわいいやつら</Text>
            </Box>
          </>
        ) : (
          <>
            <Divider />
            <Text color="#80BFFF" textAlign="center">かわいいやつら</Text>
            <Box
              bg="#95BFEA"
              width="100px"
              height="100px"
              borderRadius="50%"
              overflow="hidden"
              onClick={handleIconClick}
              ml="auto"
              mr="auto"
              cursor="pointer"
            >
              <img 
                src="/assets/cat.jpg" 
                alt="cat" 
                style={{
                  objectFit: 'cover',
                  width: '100%',
                  height: '100%',
                }}
              />
            </Box>
          </>
        )}
      </Box>      
      <Modal isOpen={isOpen} onClose={onClose}>
        <ModalOverlay />
        <ModalContent h="200px">
          <ModalHeader textAlign="center">
            モーダルだよ
          </ModalHeader>
          <ModalBody>
            <Box>
              <Text fontSize="lg" textAlign="center">
                推し とのトークを始めますか？
              </Text>
            </Box>
            <Spacer />
            <Divider mt="30px" />
            <Box display="flex" justifyContent="center">
              <Button colorScheme="gray" mx="2" onClick={onClose}>
                キャンセル
              </Button>
              <Button bg="#BF80FF" mx="2" onClick={handleStartClick}>
                <Text color="#FFFFFF">スタート</Text>
              </Button>
            </Box>
          </ModalBody>
        </ModalContent>
      </Modal>
    </>
   )}
```

# 他の画面について

他の画面については簡単にまとめておきます。

「推し」エリアのアイコンをクリックすると遷移するトーク画面がこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/db296292-3dab-54e0-ea91-afa8478a6370.png)

トーク画面のヘッダーにあるアイコンをクリックすると遷移するメッセージ作成画面がこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/c1036425-3f4b-d30c-b4f4-7f1a91677c3e.png)

メッセージ作成後に遷移するトーク画面がこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/0891d126-4783-3255-8ea2-d2c34f1cfa56.png)


# おわりに
頭を整理して上手く実装できるように力をつけていきたいです。