前回JavaScriptで作ったTodoアプリをReactで作った時のメモです。
完成イメージ
実装
JSX
まずはJSXで基本的なHTML部分を書いときます。
import React from "react";
import "./styles.css";
export const App = () => {
return (
<>
<div className="input-area">
<input placeholder="TODOを入力" />
<button>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
<div className="list-row">
<li>ああああ</li>
<button>完了</button>
<button>削除</button>
</div>
<div className="list-row">
<li>いいいい</li>
<button>完了</button>
<button>削除</button>
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
<div className="list-row">
<li>いいいい</li>
<button>戻る</button>
</div>
</ul>
</div>
</>
);
};
Reactの実装を意識した形にする
Todoの部分をstateにして初期値を設定しておきます。
import React from "react";
import { useState } from "react"; // 追加
import "./styles.css";
export const App = () => {
//-----------------------追加-------------------------------------
const [incompleteTodos, setIncompleteTodos] = useState([
"ああああ",
"いいいい"
]);
const [completeTodos, setCompleteTodos] = useState(["うううう"]);
//---------------------------------------------------------------
return (
<>
<div className="input-area">
<input placeholder="TODOを入力" />
<button>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
//-----------------変更------------------------
{incompleteTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>完了</button>
<button>削除</button>
</div>
);
})}
//----------------------------------------------
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
//-----------------変更------------------------
{completeTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>戻る</button>
</div>
);
})}
//----------------------------------------------
</div>
</ul>
</div>
</>
);
};
解説
stateとして定義したincompleteTodos
とcompleteTodos
をmapを使ってループしています。
mapなどを使用するときはkey
の設定を忘れずに!
Todo追加
import React from "react";
import { useState } from "react";
import "./styles.css";
export const App = () => {
const [todoText, setTodoText] = useState(""); //追加
const [incompleteTodos, setIncompleteTodos] = useState([
"ああああ",
"いいいい"
]);
const [completeTodos, setCompleteTodos] = useState(["うううう"]);
//-----------------------追加--------------------------------
const onChangeTodoText = (e) => setTodoText(e.target.value);
const onClickAdd = () => {
if (todoText == "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
//------------------------------------------------------------
return (
<>
<div className="input-area">
//-----------------変更----------------------
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
//------------------------------------------
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>完了</button>
<button>削除</button>
</div>
);
})}
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>戻る</button>
</div>
);
})}
</div>
</ul>
</div>
</>
);
};
解説
Reactでinput部分はvalueにstateを持たせinputにonChange
イベントを使い以下のような関数を書くのは定番。
const onChangeTodoText = (e) => setTodoText(e.target.value);
追加ボタンにonClick
イベントでTodoを追加する関数(onClickAdd
)を定義してスプレッド構文を使うことで追加している。
if (todoText == "") return;
この部分は空文字の場合はreturnすることで空文字投稿を防いでいる。
Todo削除
import React from "react";
import { useState } from "react";
import "./styles.css";
export const App = () => {
const [todoText, setTodoText] = useState("");
const [incompleteTodos, setIncompleteTodos] = useState([
"ああああ",
"いいいい"
]);
const [completeTodos, setCompleteTodos] = useState(["うううう"]);
const onChangeTodoText = (e) => setTodoText(e.target.value);
const onClickAdd = () => {
if (todoText == "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
//---------------追加------------------------
const onClickDelete = (index) => {
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
//------------------------------------------
return (
<>
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo,index) => { //追加
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>完了</button>
<button onClick={() => onClickDelete(index)}>削除</button> //追加
</div>
);
})}
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>戻る</button>
</div>
);
})}
</div>
</ul>
</div>
</>
);
};
解説
削除ボタンにonClick
イベントでonClickDelete
関数を定義。mapと関数の引数にindex
を渡すことで何番目のTodoの削除ボタンが押されたかどうかを判定できる。(関数に引数を渡すときはアロー関数を使う)
splice
を使って削除している。
Todo完了
import React from "react";
import { useState } from "react";
import "./styles.css";
export const App = () => {
const [todoText, setTodoText] = useState("");
const [incompleteTodos, setIncompleteTodos] = useState([
"ああああ",
"いいいい"
]);
const [completeTodos, setCompleteTodos] = useState(["うううう"]);
const onChangeTodoText = (e) => setTodoText(e.target.value);
const onClickAdd = () => {
if (todoText == "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
//-------------------------追加-----------------------------------------
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
//--------------------------------------------------------------------
return (
<>
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo,index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickComplete(index)}>完了</button> //追加
<button onClick={() => onClickDelete(index)}>削除</button>
</div>
);
})}
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>戻る</button>
</div>
);
})}
</div>
</ul>
</div>
</>
);
};
Todo戻す
import React from "react";
import { useState } from "react";
import "./styles.css";
export const App = () => {
const [todoText, setTodoText] = useState("");
const [incompleteTodos, setIncompleteTodos] = useState([
"ああああ",
"いいいい"
]);
const [completeTodos, setCompleteTodos] = useState(["うううう"]);
const onChangeTodoText = (e) => setTodoText(e.target.value);
const onClickAdd = () => {
if (todoText == "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
//-------------------------追加-----------------------------------------
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setCompleteTodos(newCompleteTodos);
setIncompleteTodos(newIncompleteTodos);
};
//--------------------------------------------------------------------
return (
<>
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo,index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickComplete(index)}>完了</button>
<button onClick={() => onClickDelete(index)}>削除</button>
</div>
);
})}
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo, index) => { //追加
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickBack(index)}>戻る</button> // 追加
</div>
);
})}
</div>
</ul>
</div>
</>
);
};
解説
完了の処理は 未完了リストから削除 → 完了リストに追加
戻るの処理はその逆なので今までの追加と削除の処理を組み合わせることで実装できる。
コンポーネント分割
最後にコンポーネント分割して完成。
- JSX
import React from "react";
import { useState } from "react";
import "./styles.css";
export const App = () => {
const [todoText, setTodoText] = useState("");
const [incompleteTodos, setIncompleteTodos] = useState([
"ああああ",
"いいいい"
]);
const [completeTodos, setCompleteTodos] = useState(["うううう"]);
const onChangeTodoText = (e) => setTodoText(e.target.value);
const onClickAdd = () => {
if (todoText == "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setCompleteTodos(newCompleteTodos);
setIncompleteTodos(newIncompleteTodos);
};
return (
<>
//-----------------------------変更------------------------------------------
<InputTodo
todoText={todoText}
onChange={onChangeTodoText}
onClick={onClickAdd}
/>
<IncompleteTodos
todos={incompleteTodos}
onClickComplete={onClickComplete}
onClickDelete={onClickDelete}
/>
<CompleteTodos completeTodos={completeTodos} onClickBack={onClickBack} />
//-------------------------------------------------------------------------
</>
);
};
- 完了リスト
import React from "react";
export const CompleteTodos = (props) => {
const { completeTodos, onClickBack } = props;
return (
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickBack(index)}>戻る</button>
</div>
);
})}
</ul>
</div>
);
};
- 未完了リスト
import React from "react";
export const IncompleteTodos = (props) => {
const { todos, onClickComplete, onClickDelete } = props;
return (
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
{todos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickComplete(index)}>完了</button>
<button onClick={() => onClickDelete(index)}>削除</button>
</div>
);
})}
</ul>
</div>
);
};
- Input
import React from "react";
export const InputTodo = (props) => {
const { todoText, onChange, onClick } = props;
return (
<div className="input-area">
<input placeholder="TODOを入力" value={todoText} onChange={onChange} />
<button onClick={onClick}>追加</button>
</div>
);
};
参考