[前回] Web3.0検証(14)-MeteorでTODO管理アプリの開発(新しいタスクの追加機能)
はじめに
今回は、TODO管理アプリにタスク更新/削除の機能を追加します。
理論武装: Reactでフォーム要素の制御
フォーム要素の状態保持の違い
- HTMLの場合
- 状態保持は、フォーム要素自身で行う
- フォーム要素は、
<input>
、<textarea>
、<select>
など
- フォーム要素は、
- 状態更新は、ユーザ入力に基づく
- 状態保持は、フォーム要素自身で行う
- Reactの場合
- 状態保持は、コンポーネントの
state
プロパティにて - 状態更新は、setState()関数でのみ
- 状態保持は、コンポーネントの
Reactでフォーム要素のハンドリング方法2点
- 制御コンポーネント(controlled component)
- Reactコンポーネントが、フォーム要素のユーザ入力をハンドリング
-
state
の更新に対し、イベントハンドラを書く - Reactの
state
を信頼できる唯一情報源(single source of truth)
とみなす
-
- メリット
-
state
値を、他のUI要素に渡したり、他のイベントハンドラからリセット可能に
-
- Reactコンポーネントが、フォーム要素のユーザ入力をハンドリング
- 非制御コンポーネント(uncontrolled component)
- DOMが、フォーム要素のユーザ入力をハンドリング
-
ref
を使用し、DOMからフォーム要素値を取得 - DOM(Document Object Model)とは
- マークアップされたリソース(Document)をリソース要素(Object)の木構造(Model)で表現し操作可能にする仕組み
- HTMLやXMLなどマークアップされたドキュメントを、オブジェクトの木構造モデルで表現することで、プログラムから操作・利用可能にする
- Documentの種類、操作に用いるプログラミング言語の種類に依存しない仕様
-
- DOMが、フォーム要素のユーザ入力をハンドリング
フォーム要素ハンドリングのサンプルコード
- 制御コンポーネントで
Name
値を受け取る例- フォーム要素にvalue属性を設定し、常に
this.state.value
を表示 -
handleChange
はユーザ入力により実行され、Reactのstate
を更新
- フォーム要素にvalue属性を設定し、常に
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('The name is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
- 非制御コンポーネントで
Name
値を受け取る例- フォーム要素に
ref
属性を設定 -
ref
の値this.input
を使用し、フォーム要素値を取得
- フォーム要素に
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('The name is: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
ここから、開発続行
端末1から、Meteorアプリを実行
$ cd simple-todos-react
$ meteor run
[[[[[ ~/simple-todos-react ]]]]]
=> Started proxy.
=> Started HMR server.
=> Started MongoDB.
=> Started your app.
=> App running at: http://localhost:3000/
端末2から、フォームにチェックボックスを追加
-
Task
コンポーネントにcheckbox要素を追加-
checkbox
の状態更新にonChange
イベントを使用しないため、readOnly
属性指定 -
checked
属性をboolean
値に指定し、非制御コンポーネントから制御コンポーネントに切り替える - コールバック
onCheckboxClick
を受け取る- チェックボックスがクリックされたときに呼び出される関数
-
imports/ui/Task.jsx
import React from 'react';
export const Task = ({ task, onCheckboxClick }) => {
return (
<li>
<input
type="checkbox"
checked={!!task.isChecked}
onClick={() => onCheckboxClick(task)}
readOnly
/>
<span>{task.text}</span>
</li>
);
};
- チェックボックスの切り替え
-
isChecked
フィールドを切り替えることで、タスクのドキュメントを更新可能に - ドキュメントを変更する関数を作成し、
Task
コンポーネントに渡す
-
imports/ui/App.jsx
import React from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { Task } from './Task';
import { TasksCollection } from '/imports/api/TasksCollection';
import { TaskForm } from './TaskForm';
const toggleChecked = ({ _id, isChecked }) => {
TasksCollection.update(_id, {
$set: {
isChecked: !isChecked
}
})
};
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch());
return (
<div>
<h1>Welcome to Meteor!</h1>
<TaskForm/>
<ul>
{ tasks.map(task => <Task key={ task._id } task={ task } onCheckboxClick={toggleChecked} />) }
</ul>
</div>
);
};
ブラウザでアプリを確認
ブラウザから、http://localhost:3000/
にアクセス。
タスク毎に、チェックボックスが追加されています。
タスクの削除機能
- まず、
Task
コンポーネントで、削除ボタンを追加し、コールバック関数を受け取る
imports/ui/Task.jsx
import React from 'react';
export const Task = ({ task, onCheckboxClick, onDeleteClick }) => {
return (
<li>
<input
type="checkbox"
checked={!!task.isChecked}
onClick={() => onCheckboxClick(task)}
readOnly
/>
<span>{task.text}</span>
<button onClick={ () => onDeleteClick(task) }>×</button>
</li>
);
};
- つぎ、アプリに削除ロジックを追加
- タスク削除用関数を作成
-
Task
コンポーネントのコールバックプロパティonDeleteClick
に、この関数を指定
imports/ui/App.jsx
const deleteTask = ({ _id }) => TasksCollection.remove(_id);
export const App = () => {
..
<ul>
{ tasks.map(task => <Task
key={ task._id }
task={ task }
onCheckboxClick={toggleChecked}
onDeleteClick={deleteTask}
/>) }
</ul>
..
}
再度、ブラウザからアプリを確認
タスク別、末尾に削除ボタンが追加されました。
試しに、先頭My new task
のx
ボタンをクリックしてみます。
すると、タスク消えました。やったー。
おわりに
タスクの更新/削除機能を追加しました。
次回もアプリ開発続きます。お楽しみに。