フロントエンドエンジニアを目指しReactを学んでいます。
この記事では、何回かに分けてReactのHooksを使ったTodoリスト作成をまとめていきます。目的は学びを整理するためです。
記事の構成
記事は、以下のような三部作にする予定です。
- Todoリストの登録機能と削除機能を実装する
- localStrogeを使った永続化と編集機能を実装する
- ログを実装する
①は、「初心者の挑戦!ReactのHooksを活用してTodoリストを作ってみる【登録&削除機能編】」で実装していますので読んでみてください。
こちらの記事では「② localStrogeを使った永続化と編集機能を実装する」を行います。
記事をおすすめする人
- プログラミング初心者でReactに興味がある人
- ReactのTodoリスト作成で躓いている人
- ReactのHooksについて学んでいる人
記事を読む上での注意点
デザインにはTailwind CSSを使用していますが、ReactやTailwind CSSの設定方法などにも触れません。
コードがまだまだ未熟ですので、アドバイスいただけると幸いです。
それでは実装スタート
編集機能を実装する
const [todoEditing, setTodoEditing] = useState(null);
const [editingText, setEditingText] = useState("");
まずはuseStateを使って、上記の2つの構文を作ります。
上の構文で(null)を指定している理由は、todoEditingに渡ってくる値がidであるためです。
idには何の変更も加えないため null にしてあります。
<button
onClick={() => submitEdits(todoList.id)}
className="ml-7"
>
再投稿
</button>
<button
onClick={() => setTodoEditing(todoList.id)}
className="ml-7"
>
編集
</button>
</li>
</ul>
次に編集ボタンと再投稿ボタンの作成に移ります。
編集用と再投稿用のタグを作り、それぞれに onClick イベントハンドラを指定し、関数を書きます。
編集は setTodoEditing、再投稿はsubmitEdits内でそれぞれtodoList.idを呼び出すようにします。
すると、画像のように再投稿と編集ボタンが一緒に表示されました。
これだとちょっと微妙なので以下の処理をしたいと思います。
- 普段は編集ボタンだけが表示されるようにする。
- 編集ボタンを押した該当のタスクだけ再投稿ボタンに切り替わるようにする。
{todoList.id === todoEditing ? (
<button
onClick={() => submitEdits(todoList.id)}
className="ml-7"
>
再投稿
</button>
) : (
<button
onClick={() => setTodoEditing(todoList.id)}
className="ml-7"
>
編集
</button>
)}
</li>
</ul>
まずは、①と②の処理から行います。考えた処理を実行するためには、三項演算子が使えます。
三項演算子で2つの要素を囲い、「todoEditingとtodoList.idが同じ場合は、再投稿ボタンを表示し、それ以外は編集ボタンを表示しろ」という命令を出します。
すると、編集ボタンを押したら再投稿のボタンに切り替えるという処理ができました!
次に、編集を行う際のフォームから作っていきます。
{todoLists.map((todoList) => {
return (
<>
{todoEditing === todoList.id ? (
<input
type="text"
placeholder="編集内容を入力"
className="m-7 p-3 w-4/5 border-2"
value={editingText}
onChange={(e) => setEditingText(e.target.value)}
/>
) : (
<div>{""}</div>
)}
今回は、第1回目の記事で実装した削除ボタンの上に編集用の入力欄を作りたいと思います。
まず、タグでtype=”text” 、value=”{editingText}”を定義し、onChang= { (e) => setEditingText ( e.target.value)} というイベントハンドラで「入力された値をsetEditingText内で呼び出しなさい」 と命令します。
そして最後に再び三項演算子を使って、todoEditing と todoList.idが同じ時は編集用のインプとフォームを表示して、そうでないときはからの配列を返すようにしました。
正直、これが正解かは自信がありません。加えて、今のままでは入力フォームが空の場合でも編集が反映されてしまいます。
if (input !== “”)を入れればいいのかな?と思い試しましたがうまくできませんでした。これは、次の課題としたいと思います。アドバイスもらえたら嬉しいです。
localStorageにデータを保存する
次にlocalStorageに記入したデータを保存して、リロードしても入力したデータが消えないよう実装していきます。
この処理を実行するには、ReactのHooksのひとつである「useEffect」を使用します。useEffect1を使うことでレンダーの結果が反映された後に処理を実行できます。
useEffect(() => {
const temp = JSON.stringify(todoLists);
localStorage.setItem("keepTodo", temp);
}, [todoLists]);
JSON.stringifyメソッドを使ってtodoLists内に格納されたデータを Javascriptの文字列に変換できます。そして変換したJavascript文字列を任意の変数(今回はtemp)に代入します。
そして、localStorage.setItemメソッドを活用して入力データを保存するための命令を書きます。引数内にある(”① keepTodo” , ② temp)ですが、 ①は、この後保存したデータを呼び出すさいのキーとして使われます。任意の文字列を入れましょう。②は、先程定義した変数tempを入れ呼び出します。
第二引数内の [ ] は、useEffectを実行するトリガーを決めるものです。[ ] にはStateやpropsが入れられ、入れ込んだStateやpropsの値が変わるたびに処理を実行します。
入れた何も入力しなければ1度だけ関数内の処理が実行されます。
useEffect(() => {
const temp = localStorage.getItem("keepTodo");
const loadedTodo = JSON.parse(temp);
if (loadedTodo) {
setTodoLists(loadedTodo);
}
}, []);
次に保存したデータを呼び出す処理を書きます。
localStorage.getItemメソッドを活用して、先程設定した文字列(”keepTodo” )を呼び出しtemp内に代入します。
次に JSON.parseメソッドを使って、temp内に格納されたデータをJavascriptの値や文字列として構成しなおし、それをloadedTodoに格納します。
最後に、これがリロードによる呼び出しであることを明確にするため条件分岐 if(loadedTodo )を使って、「loadedTodoの処理だった場合にのみ、setTodoListsで(loadedTodo)を呼び出し実行しろ」という支持を出します。
これで、リロードしてもデータが残るようになりました!
現時点でのコード
import React, { useState, useEffect } from "react";
const App = () => {
const [input, setInput] = useState("");
const [todoLists, setTodoLists] = useState([]);
const [todoEditing, setTodoEditing] = useState(null);
const [editingText, setEditingText] = useState("");
useEffect(() => {
const temp = localStorage.getItem("keepTodo");
const loadedTodo = JSON.parse(temp);
if (loadedTodo) {
setTodoLists(loadedTodo);
}
}, []);
useEffect(() => {
const temp = JSON.stringify(todoLists);
localStorage.setItem("keepTodo", temp);
}, [todoLists]);
const handleOnSubmit = () => {
const newTodo = {
id: Math.floor(Math.random() * 1000),
value: input,
};
if (input !== "") setTodoLists([...todoLists, newTodo]);
setInput("");
};
const addTodo = (e) => {
e.preventDefault();
handleOnSubmit();
};
const deleteListButton = (id) => {
const newArray = todoLists.filter((todoLists) => todoLists.id !== id);
setTodoLists(newArray);
};
const submitEdits = (id) => {
const updatedTodoLists = [...todoLists].map((todoList) => {
if (todoList.id === id) {
todoList.value = editingText;
}
return todoList;
});
setTodoLists(updatedTodoLists);
setTodoEditing(null);
setEditingText("");
};
return (
<>
<h1 className="text-center text-3xl font-bold mt-10">
Todo List Practice
</h1>
<div className="text-center mt-7">
<form onSubmit={addTodo}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="タスクを入力"
className=" p-3 w-4/5 border-2"
/>
<button
type="submit"
className="bg-indigo-700 font-semibold text-white py-2 px-4 rounded m-6 w-3/4 container mx-auto"
>
追加
</button>
</form>
{/* 以降map開始 */}
<div className="mt-3 container mx-auto">
{todoLists.map((todoList) => {
return (
<>
{todoEditing === todoList.id ? (
<input
type="text"
placeholder="編集内容を入力"
className="m-7 p-3 w-4/5 border-2"
value={editingText}
onChange={(e) => setEditingText(e.target.value)}
/>
) : (
<div>{}</div>
)}
<ul className="flex justify-between mx-20">
<li key={todoList.id}>{todoList.id}</li>
<li>{todoList.value}</li>
<li>
<button onClick={() => deleteListButton(todoList.id)}>
削除
</button>
{todoList.id === todoEditing ? (
<button
onClick={() => submitEdits(todoList.id)}
className="ml-7"
>
再投稿
</button>
) : (
<button
onClick={() => setTodoEditing(todoList.id)}
className="ml-7"
>
編集
</button>
)}
</li>
</ul>
</>
);
})}
</div>
</div>
</>
);
};
export default App;
まとめ
第2回目では、編集機能の実装とlocalStorageにデータを保存しリロードしてもリストが残るような実装をしました。
三項演算子やuseEffectなど新しい技も出てきました。別の場面でも応用して使えるように基礎をしっかり固めたいです。
また、現時点では編集機能が空でも入力できてしまったり、全削除機能がないなどいくつか課題も見えています。それらは解決策を考えて、次回に持ち越したいと思います。