この記事は下記記事の続きになります。
前回まではアプリの概要と環境設定(というってもcodesandboxですが…笑)
本記事ではアプリのソースをどんどん書いていきますよ!
以下の3ステップでTODOアプリを作成していこうと思います。
- TODOリストを画面に描画するロジックを作成
- TODOリストをそれっぽくスタイルしていく
- TODOを1つ新規追加するロジックを作成
TODOリストを画面に描画するロジックを作成
さて、コード書いていきましょう!
TODOリストを内部で保持しておく箱(オブジェクト)を作成
import { useState } from "react";
import "./styles.css";
export default function App() {
const [todoList, setTodoList] = useState([
{
id: 1,
content: "TODOテストNo1",
isDone: false
},
{
id: 2,
content: "TODOテストNo2",
isDone: true
}
]);
return (
<div className="App">
Hello
</div>
);
}
ここで着目することは2点あります。
1つ目は「HookのuseState」を使用していることです。
useStateを使用することにより初期値の定義とそのセッターメソッドを一気に定義することができます。
また、このstateが更新されることでコンポーネント自体が再レンダーされるため、新規でTODOが追加された時に表示を更新させることができます。
2つ目は「初期値をオブジェクトIN配列」の形で初期値を定義していることです。
配列形式でTODOのみを保持しても良かったのですが、実装したい1機能にてTODOの完了/未完了の状態を保持する必要があったので、似たような変数はオブジェクトとして1つで保持している次第です。
オブジェクトIN配列という名前は以下の記事で私が勝手につけました(笑)
すごい便利なので何か名前あればいいんですけどね…
定義されたTODOを画面に表示
TODOを画面に表示する用のコンポーネントを作成します。
フォルダ構成は以下のように変更します。
![](https://atsu-developer.net/wp-content/uploads/2022/05/スクリーンショット-2022-05-24-10.28.12-1024x642.png)
ItemList.js
import {
List,
ListItem,
ListItemText
} from "@material-ui/core";
const ItemList = (props) => {
return (
<List>
{props.todoList.map((item) => {
return (
<ListItem
key={item.id}
role={undefined}
dense
button
>
<ListItemText id={item.id} primary={item.content} />
</ListItem>
);
})}
</List>
);
};
export default ItemList;
App.js
import { useState } from "react";
import ItemList from "./components/ItemList";
import "./styles.css";
export default function App() {
const [todoList, setTodoList] = useState([
{
id: 1,
content: "TODOテストNo1",
isDone: false
},
{
id: 2,
content: "TODOテストNo2",
isDone: true
}
]);
return (
<div className="App">
<ItemList todoList={todoList} />
</div>
);
}
ここで着目する点は2つあります。
1つ目はファイル構成を変更したことです。
Reactはコンポーネント指向ですので、小さな機能ごとにコンポーネントを作成しておくことが望まれます。(こんなこと言って私もまだまだですが…)
今回は分かりやすく「components」というフォルダを作成して、その中に追加のファイルを格納しました。
2つ目は親コンポーネントで保持していたstateを表示していることです。
親コンポーネントであるAppはTODOをStateにて保持しており、それを子コンポーネントであるItemListに引数として渡します。ItemListではそれをPropsにより受け取りstateを表示している様子が伺えると思います。
表示する際はjavascriptのmapメソッドを使用して表示しています。
TODOリストをそれっぽくスタイルしていく
チェックボックスを追加したり、終わったTODOが分かりやすいようにスタイルを付けてみたりしていきます。
チェックボックスの追加
ItemList.js
import {
Checkbox,
List,
ListItem,
ListItemIcon,
ListItemText,
makeStyles
} from "@material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {
width: "100%",
maxWidth: 360
},
}));
const ItemList = (props) => {
const classes = useStyles();
const handleCheckbox = (id) => {
let updatedList = props.todoList.map((item) => {
if (item.id == id) {
return { ...item, isDone: !item.isDone };
}
return item;
});
props.setTodoList(updatedList);
};
return (
<List className={classes.root}>
{props.todoList.map((item) => {
return (
<ListItem
key={item.id}
role={undefined}
dense
button
onClick={() => handleCheckbox(item.id)}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={item.isDone}
tabIndex={-1}
inputProps={{ "aria-labelledby": item.id }}
/>
</ListItemIcon>
<ListItemText id={item.id} primary={item.content} />
</ListItem>
);
})}
</List>
);
};
export default ItemList;
App.js
import { useState } from "react";
import ItemList from "./components/ItemList";
import "./styles.css";
export default function App() {
const [todoList, setTodoList] = useState([
{
id: 1,
content: "TODOテストNo1",
isDone: false
},
{
id: 2,
content: "TODOテストNo2",
isDone: true
}
]);
return (
<div className="App">
<ItemList todoList={todoList} setTodoList={setTodoList} />
</div>
);
}
ここで着目する点は1つあります。
チェックボックス押下時の動作です。
チェックボックスにチェックが入るかどうかは、Checkboxタグのchecked属性で管理しています。
その中には現在isDoneフィールドの値が代入されています。
ですので、チェックボックス押下時は対象のTODOのisDoneの値を反転させてあげる必要があります。
ここでTODOオブジェクトはApp.jsのuseStateにて持っていましたので、そのセッターメソッド(setTodo)もApp.jsからpropsで引き継いであげる必要があります。
オブジェクトIN配列の値を変更する方法はjavascript特有の記法である「…」を使ってあげることで楽に変更することができます。
詳しくは以下をご参照ください。
完了/未完了でスタイルを変えてあげる
ItemList.js
import {
Checkbox,
List,
ListItem,
ListItemIcon,
ListItemText,
makeStyles
} from "@material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {
width: "100%",
maxWidth: 360
},
finishedTodo: {
color: "#808080"
}
}));
const ItemList = (props) => {
const classes = useStyles();
const handleCheckbox = (id) => {
let updatedList = props.todoList.map((item) => {
if (item.id == id) {
return { ...item, isDone: !item.isDone };
}
return item;
});
props.setTodoList(updatedList);
};
return (
<List className={classes.root}>
{/* Not finished TODO */}
{props.todoList.map((item) => {
if (!item.isDone) {
return (
<ListItem
key={item.id}
role={undefined}
dense
button
onClick={() => handleCheckbox(item.id)}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={item.isDone}
tabIndex={-1}
inputProps={{ "aria-labelledby": item.id }}
/>
</ListItemIcon>
<ListItemText id={item.id} primary={item.content} />
</ListItem>
);
}
})}
{/* finished TODO */}
{props.todoList.map((item) => {
if (item.isDone) {
return (
<ListItem
key={item.id}
role={undefined}
dense
button
onClick={() => handleCheckbox(item.id)}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={item.isDone}
tabIndex={-1}
inputProps={{ "aria-labelledby": item.id }}
/>
</ListItemIcon>
<ListItemText
id={item.id}
primary={item.content}
className={classes.finishedTodo}
/>
</ListItem>
);
}
})}
</List>
);
};
export default ItemList;
完了のTODO(つまりisDoneがtrue)を下に、未完了のTODO(つまりisDoneがfalse)を上に表示できるようにしています。
また、完了の方にはStyleを適用させ薄暗く表示させるようにしています。
TODOを1つ新規追加するロジックを作成
新規に追加するTODOを入力するフォームが1つ必要になってきます。
今回もコンポーネント指向に従って、新しくファイルを作成します。
ファイル構成を以下のように変更します。(CreateItem.jsをcomponentフォルダに追加しました)
![](https://atsu-developer.net/wp-content/uploads/2022/05/スクリーンショット-2022-05-24-13.05.42-1024x786.png)
新しく作成したファイルに以下のコードを記述していきます。
CreateItem.js
import { IconButton, TextField } from "@material-ui/core";
import { AssignmentTurnedIn } from "@material-ui/icons";
import { useState } from "react";
const CreateItem = (props) => {
const [newTodo, setNewTodo] = useState("");
const AddButton = () => {
const addButtonClick = () => {
props.setTodoList([
...props.todoList,
{
id: props.todoList.length + 1,
content: newTodo,
isDone: false
}
]);
setNewTodo("");
};
return (
<IconButton>
<AssignmentTurnedIn onClick={() => addButtonClick()} />
</IconButton>
);
};
return (
<TextField
label="TODOを追加"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
InputProps={{ endAdornment: <AddButton /> }}
/>
);
};
export default CreateItem;
App.js
import { useState } from "react";
import CreateItem from "./components/CreateItem";
import ItemList from "./components/ItemList";
import "./styles.css";
export default function App() {
const [todoList, setTodoList] = useState([
{
id: 1,
content: "TODOテストNo1",
isDone: false
},
{
id: 2,
content: "TODOテストNo2",
isDone: true
}
]);
return (
<div className="App">
<CreateItem setTodoList={setTodoList} todoList={todoList} />
<ItemList todoList={todoList} setTodoList={setTodoList} />
</div>
);
}
ここで着目する点は2つあります。
1つ目はテキスト入力の値をnewTodoというuseStateにて管理している所です。
TextFieldのvalue属性とnewTodoとを同期取らせています。
また、onChange属性にてnewTodoのセッターメソッドを定義することで、入力が変わるたびに表示も変わり、useStateの値も変わるというわけです。
2つ目はstateを更新する時のロジックです。
次に「新規登録」ボタンを押下した時はnewTodoの値を親コンポーネントから取得したtodoListのセッターメソッドに追加するようにしています。
ここの辺が2つのuseStateが絡んでくるので少しややこしいですね。
最後に
レスポンシブを意識させたり、編集削除機能を作ってみたり、DBと接続してみたり、スタイルにこだわってみたりとTodoアプリ一つで開発の幅は無限にあると思います。
コメント