Next.jsでTODOアプリを作成する日記①

# はじめに
こちらの記事でNext.jsの導入方法について教えて頂き、ローカル環境にNext.js + TypeScriptをセットアップする事ができました。

https://qiita.com/konno5757/items/942b32a7e16883ebff48

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/a3a88750-94a5-a808-628a-8bf1fe6c5ccf.png)

せっかくセットアップしたのでTODOアプリを作りながらReactの機能を確認したいと思います。
TODOアプリの仕様は「はじめに」を書いている段階では特に考えてないです。

# 記事の内容
- [x] Tailwindを導入できた
- [x] ComponentにPropsを渡せた
- [x] Componentの中でPropsを使用できた 
- [x] Componentをループを使って表示できた
- [x] React Iconsを導入できた

## 1.Nextの構成
セットアップ完了時、以下のようになフォルダ構成になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/e212d51c-1e12-3830-dc5a-7553ed8e186e.png)

Nextのルーティングはpages内のディレクトリ構造とファイル名がそのまま反映されるみたいです。この辺はNuxt(Vueの方)に似ています。上記の場合index.tsxがデフォルト画面としてアプリのルートURLにアクセスした時に表示されます。

pagesディレクトリの残り2つの_app.tsx,_document.tsxについてはこちらが大変参考になりました。

https://qiita.com/kamenaris/items/8bcb407007501326bd9d

またAPIも使用しないためapiディレクトリについても割愛します。
今後このTodoアプリが発展して、外部APIにリクエストを送るようになったら使いたい…

https://nextjs-ja-translation-docs.vercel.app/docs/api-routes/introduction

Nextセットアップの記事でcomponentsディレクトリを作成し、そこに画面を形成する部品(Button)を作成しています。

余談ですが、昨今のJavascriptsフレームワークは色々なUIをComponentとして定義する中で画面やレイアウト、UIパーツが複雑なネスト構造になりがちです。
そのため大規模なアプリはUIデザインパターンのようなルールに則って構造を整理するみたいです。

Atomic Designなど有名で参考にした事があります。(がこの記事ではあまり気にせずに作ります)

https://bradfrost.com/blog/post/atomic-web-design

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/bbe1f89d-79b0-5115-e7da-ae1180361d1f.png)



## 2.Tailwindの導入
[Tailwindの公式ドキュメント](https://tailwindcss.com/docs/guides/nextjs)に従います。

パッケージ管理ソフトを使用してTailwindのパッケージをインストールします。
```
node -v
v19.9.0

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

tailwind.config.jsを設定します。
```tsx:jstailwind.config.js
module.exports = {
	content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
    // srcディレクトリの全てのファイルにTailwindを適用する 
	theme: {
		extend: {},
	},
	plugins: [],
};
```

global.cssは多分名前からしてグローバルに適用されるスタイルと思われるので、邪魔な部分は消してTailwindの読み込みだけを残しました。
```css:global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

確認のためindex.tsxに簡単な記述をして画面表示させてみます。
```tsx:pages/index.tsx
export default function Home() {
	return (
		<div>
			<h1 className="text-xl font-bold text-green-400">Hello World</h1>
            // フォントサイズ大きめ、緑で太字のHello World
		</div>
	);
}

```

微調整の際flexボックスやmarginに関するTailwindのUnitiesクラスを設定する際はチートシートが非常に便利です。
>**[Tailwind CSS Cheat Sheet](https://tailwindcomponents.com/cheatsheet/)**

開発モードでNext.jsアプリケーションを実行してみます。
```
npm run dev

> next-tutorial@0.1.0 dev
> next dev

   ▲ Next.js 14.0.2
   - Local:        http://localhost:3000

 ✓ Ready in 4.1s
```

やったー(^^)!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/65153541-d643-0ff5-faad-ea9c074f7c76.png)

## 3.見た目を探す
TODOアプリはやる事リストを追加したり削除したり完了させたりするアプリにしたいです。

私は見た目から入った方がやる気がでるのでTailwindが提供するかっこいいUIデザインから良さそうなコンポーネントを探します。
こちらを参考にさせて頂きテンプレを探しました。

https://qiita.com/Masahiro111/items/f7d6ad8280ae92717f0f

今回はこのUIをTODO項目に使う事にします。

https://merakiui.com/components/application-ui/alerts

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/d8d7d1c2-edc8-81e8-012a-062694e481c1.png)

上記をcomponentsにTodoItemコンポーネントとして定義します。
```tsx:components/Todo/TodoItem.tsx
const TodoItem = () => {
	return (
		<div className="flex w-full border border-gray-300 max-w-sm overflow-hidden bg-white rounded-lg shadow-md dark:bg-gray-800">
			<div className="flex items-center justify-center w-12 bg-emerald-500">
				<svg
					className="w-6 h-6 text-white fill-current"
					viewBox="0 0 40 40"
					xmlns="http://www.w3.org/2000/svg">
					<path d="M20 3.33331C10.8 3.33331 3.33337 10.8 3.33337 20C3.33337 29.2 10.8 36.6666 20 36.6666C29.2 36.6666 36.6667 29.2 36.6667 20C36.6667 10.8 29.2 3.33331 20 3.33331ZM16.6667 28.3333L8.33337 20L10.6834 17.65L16.6667 23.6166L29.3167 10.9666L31.6667 13.3333L16.6667 28.3333Z" />
				</svg>
			</div>

			<div className="px-4 py-2 -mx-3">
				<div className="mx-3">
					<span className="font-semibold text-emerald-500 dark:text-emerald-400">
						完了
					</span>
					<p className="text-sm text-gray-600 dark:text-gray-200">
						TodoアイテムとFormを作成する
					</p>
				</div>
			</div>
		</div>
	);
};

export default TodoItem;

```
## 4.Componentを使う
index.tsxでTodoItemをインポートして表示してみます。
```tsx:pages/index.tsx
import TodoItem from "../components/Todo/TodoItem";

export default function Home() {
	return (
		<div>
			<h1 className="text-xl font-bold text-green-400">Hello World</h1>
			<TodoItem />
		</div>
	);
}
```

やったー(^^)!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/6967e0f2-3f41-fb18-06f5-ca09c79ba1cd.png)


同じようにTodoを作成するためのFormをTaillwindのテンプレから作成しました。
```tsx:components/Todo/TodoForm.tsx
const TodoItem = () => {
	return (
		<div className="w-80 p-1 overflow-hidden border border-solid border-gray-200 bg-white rounded-lg shadow-md dark:bg-gray-800">
			<div className="px-4 py-2 -mx-3">
				<div className="mx-3">
					<span className="font-semibold text-emerald-500 dark:text-emerald-400">
						Success
					</span>
					<p className="text-sm text-gray-600 dark:text-gray-200">
						Your account was registered!
					</p>
				</div>
			</div>
		</div>
	);
};

export default TodoItem;

```

アプリのイメージを固めたいので、とにかくいい感じの見た目を作ってみます。
チートシートを見ながらレイアウトやデザインを整えます。

```tsx:pages/index.tsx
import TodoItem from "../components/Todo/TodoItem";
import TodoForm from "@/components/Todo/TodoForm";

export default function Home() {
	return (
		<>
			<div className="w-100 flex justify-center">
				<div className="my-5">
					<h1 className="text-xl font-bold text-green-400">Hello World</h1>
					<TodoItem />
					<TodoItem />
					<TodoItem />
					<TodoForm />
				</div>
			</div>
		</>
	);
}

```

やったー!(^^)!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/975eb276-aae5-d886-ab2f-cb2e981ba8cc.png)

## 6.Componentにデータを渡す
このままではTODOの内容が全て同じ固定値なので与えられたデータに沿って表示を変更できるようにしたいです。

Componentに値を渡す時にその値はPropsという呼び方をするそうです。

セットアップ記事で作成されたButtonコンポーネントにお手本が書いてありました。
```tsx:components/Button/buttontsx
import styles from "./button.module.css";
type ButtonProp = {
	children: string;
};

const Button = (props: ButtonProp): JSX.Element => {
	return (
		<button type="button" className={styles.red}>
			{props.children}
		</button>
	);
};

export default Button;
```

Propsの型定義をしてその値をComponentで受け取る形となっています。

同じようにTodoItemでもPropsを定義しその値を受け取り表示してみます。
>[ReactコンポーネントへのPropsの渡し方/受け取り方大全](https://zenn.dev/ynakamura/articles/e562376735d398)

受け取り側(子)で型定義をしてPropsで利用方法を設定する
>[TypeScriptの型定義まとめ【Reactも対応】](https://zenn.dev/akkie1030/articles/9f2304544245b2)
```tsx:comonents/Todo/todoItem.tsx

// propsとしてtitle,contentを受け付ける設定
type TodoItemProps = {
	title: string;
	content: string;
};

const TodoItem = (props: TodoItemProps) => {
	return (
		<div className="flex w-full border border-gray-300 max-w-sm overflow-hidden bg-white rounded-lg shadow-md dark:bg-gray-800">
			<div className="flex items-center justify-center w-12 bg-emerald-500">
				<svg
					className="w-6 h-6 text-white fill-current"
					viewBox="0 0 40 40"
					xmlns="http://www.w3.org/2000/svg">
					<path d="M20 3.33331C10.8 3.33331 3.33337 10.8 3.33337 20C3.33337 29.2 10.8 36.6666 20 36.6666C29.2 36.6666 36.6667 29.2 36.6667 20C36.6667 10.8 29.2 3.33331 20 3.33331ZM16.6667 28.3333L8.33337 20L10.6834 17.65L16.6667 23.6166L29.3167 10.9666L31.6667 13.3333L16.6667 28.3333Z" />
				</svg>
			</div>

			<div className="px-4 py-2 w-80">
				<div className="mx-3">
					<span className="font-semibold text-emerald-500 dark:text-emerald-400">
						完了
					</span>
					<p className="me-1 mb-0 text-gray-700">{props.title}</p>
					<span className="text-sm  text-gray-600 dark:text-gray-200 me-1">
						{props.content}
					</span>
				</div>
			</div>
		</div>
	);
};
export default TodoItem;

```
渡す側(親)でとりあえず固定値のTodo型データを渡す
```tsx:pages/index.tsx
import TodoItem from "../components/Todo/todoItem";
import TodoForm from "@/components/Todo/todoForm";

const todoItem = {
	title: "タイトル",
	content: "TODO内容はここに記載します。",
};

export default function Home() {
	return (
		<>
			<div className="w-100 flex justify-center">
				<div className="my-5">
					<h1 className="text-xl font-bold text-green-400">Hello World</h1>
					<TodoItem {...todoItem} />
					<TodoForm />
				</div>
			</div>
		</>
	);
}
```
```tsx:jsxで式やデータ埋め込む
<p>{props.title}</p>
<span>{props.content}</span>
```

また親側ではオブジェクトをSpread構文で展開して渡しています
```tsx:スプレット構文でprops渡し
// ↓この記述でtodoオブジェクトの中身を子のpropsに展開して渡しています
//   (propsのtitleとcontentに分割して渡している)
<TodoItem {...todoItem} />
```

```tsx
// 渡し方/受け取り方大全のサンプル
const info = { name: 'John', age: 25, gender: 'male'}

<MyComponent {...info} />
// ↑の書き方は↓と同義。
<MyComponent name={info.name} age={info.age} gender={info.gender} />
```

固定値で渡したデータがTodoItemで表示されました。やったー(^^)/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/95e9f2da-2813-651a-164a-a1cb01f8b137.png)

## 7.配列をループしてComponentを表示する
TodoItemコンポーネントをリストとして表示するTodoItemListコンポーネントを作成しました。
```tsx:component/Todo/todoItemList.tsx
import TodoItem, { TodoItemProps } from "./todoItem";

// propsにdataという名前で受け皿を作っとく
type TodoItemListProps = {
	data: Array<TodoItemProps>;
};

const TodoItemList = (props: TodoItemListProps) => {
	return props.data.map((todoItem: TodoItemProps, i) => {
		return <TodoItem key={i} {...todoItem} />;
	});
};

export default TodoItemList;

```

このTodoItemListに配列としてTodoItemを渡したいです。

オブジェクトではないのでスプレッド構文を使わず、子コンポーネント側で設定したdataに対して渡そうと思います。
```tsx:component/Todo/todoItemList.tsx
import TodoForm from "@/components/Todo/todoForm";
import TodoItemList from "@/components/Todo/TodoItemList";
import { TodoItemProps } from "@/components/Todo/todoItem";

// const todoItem = {
// 	title: "タイトル",
// 	content: "TODO内容はここに記載します。",
// };

const todoItemList: TodoItemProps[] = [
	{
		title: "タイトル",
		content: "TODO内容はここに記載します。",
	},
	{
		title: "タイトル2",
		content: "TODO内容の二番目",
	},
	{
		title: "タイトル3",
		content: "TODO内容の3番目",
	},
];

export default function Home() {
	return (
		<>
			<div className="w-100 flex justify-center">
				<div className="my-5">
					<h1 className="text-xl font-bold text-green-400">Hello World</h1>
					<TodoItemList data={todoItemList} />
					<TodoForm />
				</div>
			</div>
		</>
	);
}
```

親側で渡されたprops.data配列を
```tsx:親側
<TodoItemList data={todoItemList} />
```

子側でループさせてTodoItemを描画しています
Vue.js(Vue2まで)でこれをやる際はv-forの記述を覚えないといけないですがReactではjs関数で実現できるようです。

```tsx:子側
    return props.data.map((todoItem: TodoItemProps, i) => {
    // React DOMをループする際はkeyにインデックスを渡さないといけない
		return <TodoItem key={i} {...todoItem} />;
    })
```

TodoItemListをどのコンポーネントが保持するかは
親がTodo配列を子に渡して、子はTodoを孫に渡し表示されました。やったー!(^^)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/bea62395-c34c-4490-6939-2bf018743878.png)

## 8.状態で見た目を変える
全てのTodoが完了になっているため、「完了」「実行中」「未対応」の３つの状態を設定して状態に応じて見た目を変更したいと思います。

### React Iconの導入
状態に応じてTodoに表示するIconの色などを変更したいです。
現状SVGで扱いにくいのでReact Iconを導入してチェックアイコンを選びます。

導入方法についてこちらを参考にさせて頂きました。

https://qiita.com/pikimaru/items/bffc051a248f8e2ab2b2

```
 npm install react-icons --save
```
React IconsのサイトからImportとJSXの記述を確認できます。

FontAwesome、Bootstrap Iconなどから選び放題で素晴らしいですが今回はチェックアイコンだけもらいます。

https://react-icons.github.io/react-icons/icons/fa6/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/ea107cee-19db-9d9d-0a4f-32780fa5aa20.png)

TodoItemに状態に関するprops.statusを設定し、状態に応じて見た目を変更する修正を行いました。
```tsx:component/Todo/todoItem.tsx
import { FaCheckCircle } from "react-icons/fa";
import { FaRegCheckCircle } from "react-icons/fa";

type Status = "Done" | "Progress" | "Incomplete";

export type TodoItemProps = {
	title: string;
	content: string;
	status: Status;
};

const TodoItem = (props: TodoItemProps): JSX.Element => {
	// 状態に応じてクラス名を取得する
	let statusClassName = {
		text: "",
		textColor: "",
		bgColor: "",
	};

	switch (props.status) {
		case "Done":
			statusClassName.text = "完了";
			statusClassName.textColor = "text-emerald-500";
			statusClassName.bgColor = "bg-emerald-500";
			break;
		case "Progress":
			statusClassName.text = "実行中";
			statusClassName.textColor = "text-blue-600";
			statusClassName.bgColor = "bg-blue-600";
			break;
		case "Incomplete":
			statusClassName.text = "未対応";
			statusClassName.textColor = "text-gray-600";
			statusClassName.bgColor = "bg-gray-600";
			break;
	}

	return (
		<div className="flex w-full border border-gray-300 max-w-sm overflow-hidden bg-white rounded-lg shadow-md dark:bg-gray-800">
			<div
				className={`flex items-center justify-center w-12 ${statusClassName.bgColor}`}>
				{props.status === "Done" ? (
					<FaCheckCircle className="w-6 h-6 text-white fill-current" />
				) : (
					<></>
				)}
			</div>

			<div className="px-4 py-2 w-80">
				<div className="mx-3">
					<span className={`font-semibold ${statusClassName.textColor}`}>
						{statusClassName.text}
					</span>
					<p className="me-1 mb-0 text-gray-700">{props.title}</p>
					<span className="text-sm  text-gray-600 dark:text-gray-200 me-1">
						{props.content}
					</span>
				</div>
			</div>
		</div>
	);
};

export default TodoItem;
```

親側でもデータに状態を追記して子に渡してあげれば見た目が変わるはず…
```tsx:pages/index.tsx
import TodoForm from "@/components/Todo/todoForm";
import TodoItemList from "@/components/Todo/TodoItemList";

// const todoItem = {
// 	title: "タイトル",
// 	content: "TODO内容はここに記載します。",
// };

const todoItemList = [
	{
		title: "タイトル",
		content: "TODO内容はここに記載します。",
		status: "Done",
	},
	{
		title: "タイトル2",
		content: "TODO内容の二番目",
		status: "Progress",
	},
	{
		title: "タイトル3",
		content: "TODO内容の3番目",
		status: "Incomplete",
	},
];

export default function Home() {
	return (
		<>
			<div className="w-100 flex justify-center">
				<div className="my-5">
					<h1 className="text-xl font-bold text-green-400">Hello World</h1>
					<TodoItemList data={todoItemList} />
					<TodoForm />
				</div>
			</div>
		</>
	);
}
```
わーい(^^)v
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601137/86ca48b5-f5c8-7b24-1ea2-84546edbc246.png)

上記の修正についてポイントに感じた点をいくつか書き出します。
- **三項定理の利用**
アイコン表示の部分では状態が"Done"の時はチェックアイコンを返しますが、それ以外には空になっています。
本当は「実行中」と「未対応」に応じた何かを返したかったのですが保留です。
```tsx
{props.status === "Done" ? (
    <FaCheckCircle className="w-6 h-6 text-white fill-current" />
) : (
    <></>
)}
```
- **And演算子の利用**
書いてて気づいたのですが上記の記述はFalseで何も返さない場合こちらの方がスマートです。
↓Reactで初めてこの書き方を見て&演算子の性質みたいなものを再確認できて勉強になりました。
```tsx
{props.status === "Done" && (
    <FaCheckCircle className="w-6 h-6 text-white fill-current" />
)}
```

- **テンプレートリテラルの利用**
クラス名の部分で状態に応じて取得したクラス名を設定するのに利用しました。
場合によっては${ }の中で三項定理や&&、関数など使うと思います。
```tsx
<span className={`font-semibold ${statusClassName.textColor}`}>
```

# まとめ

長くなってきてしまったので一旦区切ります。
- [x] Tailwindを導入できた
- [x] ComponentにPropsを渡せた
- [x] Componentの中でPropsを使用できた 
- [x] Componentをループを使って表示できた
- [x] React Iconsを導入できた

非効率だったり推奨されていない書き方がありましたら教えて頂きたいです<(_ _)>

②に続く　**[Next.jsでTODOアプリを作成する日記②](https://qiita.com/naitoyuma/items/409f2e14624d8983d0cd)**