Reactで作る簡単TODOアプリ

## 1.Reactプロジェクトの作成

まずは、Reactプロジェクトを作成します。”vite”コマンドを実行して、Reactアプリをセットアップします。

プロジェクト名は「my-todo-app」です。

```
npm create vite@latest

> npx
> create-vite
|
o  Project name:
|  my-todo-app
|
```
プロジェクト名を入力すると、次はフレームワークとバリアントを選択します。

ここでは下記のように選択します。
```
o  Select a framework:
|  React
|
o  Select a variant:
|  TypeScript
```
cdコマンドで「my-todo-app」に移動して、「npm i」を実行します。

:::note info
npm iは「package.json」に明示されているすべてのパッケージをインストールする際に使用
:::


```
cd my-todo-app
npm i
```
devスクリプトを実行し、ローカルホストに接続します。
```
npm run dev
 VITE v6.2.3  ready in 1429 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
```
このような画面が出てきたら、Reactの実行環境の構築は完成です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779353/1ebbd684-ae6b-46b5-a77f-1b34821cbda5.png)

## 2.shadcn/uiのインストール
https://ui.shadcn.com/docs/installation/vite
を開いてtailwindcssをinstallするコマンドをコピーし、実行します。
```
npm install tailwindcss @tailwindcss/vite
```
src/index.cssに下記を追加します。
```
@import "tailwindcss";
```

インストールが完了したら、vscodeを開きます。
```
code .
```
vscode上でターミナルを開いて下記を実行します。
```
npm run dev
```
tsconfig.jsonを開いて色付けされている部分を追記します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779353/01aca031-d12c-4716-8cde-d9e266395f82.png)

``` tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```
tsconfig.app.jsonに色付けされている部分を追記します。
``` tsconfig.app.json
{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
    
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "include": ["src"]
}

```

そして下記コマンドを実行します。
```
npm install -D @types/node
```
vite.config.jsonを下記のように変更。
``` vite.config.js
import path from "path"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})
```
shadcn/uiの初期化します。base colorを聞かれますが、ここではSlateにします。
```
npx shadcn@latest init
...
Which color would you like to use as the base color? » Slate
```
さて初期化は完了しました。とりあえずボタンコンポーネントをインストールしてみましょう。shadcn/uiは必要なコンポーネントをインストールする必要があります。
```
npx shadcn@latest add button
```
そしてsrc/App.tsxの内容を下記に変更します。
``` App.tsx
import { Button } from "@/components/ui/button"

function App() {
  return (
    <div className="flex flex-col items-center justify-center min-h-svh">
      <Button>Click me</Button>
    </div>
  )
}

export default App
```
ローカルホストを開くとボタンが表示されています。

# 3.ToDoアプリの作成
### ToDoカードの作成
新しいコンポーネントをインストールします。
今回はCardコンポーネントにします。
```
npx shadcn@latest add card
```
そしてApp.tsxを下記のようにします。
``` App.tsx
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./components/ui/card";
import { todo } from "node:test";

export default function Home() {
  const todos = ["Reactを学ぶ", "TypeScriptを学ぶ", "Viteを学ぶ"];
  return (
    <div className="flex justify-center items-center h-screen">
      <Card>
        <CardHeader>
          <CardTitle>TODO APP</CardTitle>
        </CardHeader>
        <CardContent>
          <ul>
            {todos.map((todo) => (
              <li>{todo}</li>
            ))}
          </ul>
        </CardContent>
        <CardFooter>
          <p>Card Footer</p>
        </CardFooter>
      </Card>
    </div>
  );
}
```
デフォルトではbodyスタイルにflexがついてしまっているので、bodyのスタイルからflexは削除しましょう。
``` index.css
body {
  margin: 0;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}
```
### ボタンの追加＆ToDoの追加
``` App.tsx
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./components/ui/card";
import { todo } from "node:test";
import { useState } from "react";

export default function Home() {
  // const todos = ["Reactを学ぶ", "TypeScriptを学ぶ", "Viteを学ぶ"];
  const [todos, setTodos] = useState<string[]>([]);
  const [todo, setTodo] = useState("");
  return (
    <div className="flex justify-center items-center h-screen">
      <Card className="w-[400px]">
        <CardHeader>
          <CardTitle>TODO APP</CardTitle>
        </CardHeader>
        <CardContent>
          <div className="space-y-2">
            <input className="block w-full" placeholder="内容を追加" onChange={
              (e) => setTodo(e.target.value)}
              value={todo}
            />
          <Button className="w-full" 
          onClick={() => {
            setTodos([...todos, todo]);
            // setTodos(todos) ※駄目パターン
            setTodo("");
          }}>追加</Button>
          </div>
          <ul className="mt-4">
            {todos.map((todo) => (
              <li>{todo}</li>
            ))}
          </ul>
        </CardContent>
      </Card>
    </div>
  );
}
```
駄目パターンというのは、useStateが更新されないパターンになります。
todosは配列であるため、上記のパターンだと外見だけ考えると同じ配列を
引数に入れているため更新されません。
なので、改めて展開したあとに新しい配列として引数に入れてあげる必要があります。
```
setTodos([...todos, todo]);
```
### ToDoの削除
ToDoの削除機能を追加したいと思います。
reactのアイコンをボタンに使用して削除ボタンを作成したいので、
react-iconのインストールをします。
```
npm install react-icon
```
削除ボタンを実装したのが下記です。
``` App.tsx
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./components/ui/card";
import { todo } from "node:test";
import { useState } from "react";
import { MdDelete } from "react-icons/md";

export default function Home() {
  // const todos = ["Reactを学ぶ", "TypeScriptを学ぶ", "Viteを学ぶ"];
  const [todos, setTodos] = useState<string[]>([]);
  const [todo, setTodo] = useState("");
  return (
    <div className="flex justify-center items-center h-screen">
      <Card className="w-[400px]">
        <CardHeader>
          <CardTitle>TODO APP</CardTitle>
        </CardHeader>
        <CardContent>
          <div className="space-y-2">
            <input className="block w-full" placeholder="内容を追加" onChange={
              (e) => setTodo(e.target.value)}
              value={todo}
            />
          <Button className="w-full" 
          onClick={() => {
            setTodos([...todos, todo]);
            // setTodos(todos) ※駄目パターン
            setTodo("");
          }}>追加</Button>
          </div>
          <ul className="mt-4">
            {todos.map((todo, index) => (
              <div className="flex">
                <li>{todo}</li>
                <button 
                  className="ml-3" 
                  onClick={() => {
                    setTodos(todos.filter((_, i) => i !== index));
                  }}
                >
                  <MdDelete className="h-3 w-3" color="red" />
                </button>
              </div>
            ))}
          </ul>
        </CardContent>
      </Card>
    </div>
  );
}
```
# 4.ToDoアプリのデプロイ
Firebaseを使用してデプロイをします。
※すみません、Firebaseへのアプリ登録まではスクショを取り忘れたので
ネットで調べてください

下記のコマンドを実行。
```
npm install firebase
```
Firebase CLIのインストールをします。
```
npm install -g firebase-tools
```
インストールが完了したら、Firebaseにログインと初期化をしていきます。
```
firebase login

firebase init hosting

? Please select an option: Use an existing project
? Select a default Firebase project for this directory: my-todo-app-3e59d (my-todo-app)
i  Using project my-todo-app-3e59d (my-todo-app)

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? No
? Set up automatic builds and deploys with GitHub? No
+  Wrote public/404.html
+  Wrote public/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

+  Firebase initialization complete!
```
ではデプロイをします。
```
firebase deploy

=== Deploying to 'my-todo-app-3e59d'...

i  deploying hosting
i  hosting[my-todo-app-3e59d]: beginning deploy...
i  hosting[my-todo-app-3e59d]: found 5 files in dist
+  hosting[my-todo-app-3e59d]: file upload complete
i  hosting[my-todo-app-3e59d]: finalizing version...
+  hosting[my-todo-app-3e59d]: version finalized
i  hosting[my-todo-app-3e59d]: releasing new version...
+  hosting[my-todo-app-3e59d]: release complete

+  Deploy complete!

Project Console: https://console.firebase.google.com/project/my-todo-app-3e59d/overview
Hosting URL: https://my-todo-app-3e59d.web.app
```
``` firebase.json
{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
```
Hosting URLを検索すると、TODOアプリが表示されました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779353/738234d3-eea9-4bfd-9b1a-704452bb6349.png)



お疲れ様でした。
以下、参考にした動画になります。
https://www.youtube.com/watch?v=LrwTuMTyxJM&t=484s