の続きです。
タスクの完了
タスクのチェックボックスのイベントに応じて親のtaskStoreを更新するためにonChangeに以下の関数をセットして更新させます。
ReactではuseStateを使用すれば、セッターのような関数がついてきますがQwikでは見つからなかったので、強引にセットしています。
(やり方あったら教えてください)
<input
id={task.id}
type="checkbox"
checked={task.completed}
onChange$={() => {
const updatedTasks = taskStore.tasks.map((task: any) => {
if (id === task.id) {
return {...task, completed: !task.completed}
}
return task
})
taskStore.tasks = updatedTasks;
}}
/>
タスクの削除
Deleteボタンにもクリックイベントに対する処理を追記していきます。
こちらもタスクの完了の関数とどうようにtaskStoreを更新することでデータの管理をしています。
<button
type="button"
className="btn btn__danger"
onClick$={() => {
const remainingTasks = taskStore.tasks.filter((task: any) => id !== task.id)
taskStore.tasks = remainingTasks;
}}
>
Delete <span className="visually-hidden">Eat</span>
</button>
ビューテンプレートとエディットテンプレートの切り替え
editingTemplateとviewTemplateに分けます。
useSignal
を使用して、isEditing を定義しisEditing.value
のbool値によってテンプレートの出し分けをします。
import { component$, useSignal } from "@builder.io/qwik";
type Todo = {
taskStore: any
id: string
}
export default component$(({ taskStore, id }: Todo) => {
const isEditing = useSignal(false);
const task = taskStore.tasks.filter((item: any) => item.id === id)[0];
const editingTemplate = (
<>
<form className="stack-small">
<div className="form-group">
<label className="todo-label" for={task.id}>
New name for {task.name}
</label>
<input id={task.id} className="todo-text" type="text" />
</div>
<div className="btn-group">
<button
type="button"
className="btn todo-cancel"
onClick$={() => {
isEditing.value = false;
}}
>
Cancel
<span className="visually-hidden">renaming {task.name}</span>
</button>
<button type="submit" className="btn btn__primary todo-edit">
Save
<span className="visually-hidden">new name for {task.name}</span>
</button>
</div>
</form>
</>
);
const viewTemplate = (
<>
<div className="stack-small">
<div className="c-cb">
<input
id={task.id}
type="checkbox"
checked={task.completed}
onChange$={() => {
const updatedTasks = taskStore.tasks.map((task: any) => {
if (id === task.id) {
return {...task, completed: !task.completed}
}
return task
})
taskStore.tasks = updatedTasks;
}}
/>
<label className="todo-label" for="todo-0">
{task.name}
</label>
</div>
<div className="btn-group">
<button
type="button"
className="btn"
onClick$={() => {
isEditing.value = true
}}
>
Edit <span className="visually-hidden">Eat</span>
</button>
<button
type="button"
className="btn btn__danger"
onClick$={() => {
const remainingTasks = taskStore.tasks.filter((task: any) => id !== task.id)
taskStore.tasks = remainingTasks;
}}
>
Delete <span className="visually-hidden">Eat</span>
</button>
</div>
</div>
</>
);
return (
<li className="todo">{isEditing.value ? editingTemplate : viewTemplate}</li>
)
})
このようにEditボタンをクリックすると、編集用のテキストボックスに切り替わるはずです。
エディットページを完成させる
エディットページのinputの入力イベントに対するStateの更新処理を追記(onChangeの箇所)して、Saveボタンが押されるとそのStateでnameを更新するようにします。
ここでも preventdefault:click
をタグに追記してあげないとボタンを押すたびにStateがリセットされてしまうので気をつけてください。
const editingTemplate = (
<>
<form className="stack-small">
<div className="form-group">
<label className="todo-label" for={task.id}>
New name for {task.name}
</label>
<input
id={task.id}
className="todo-text"
type="text"
onChange$={(e) => {
nameStore.name = e.target.value
}}
/>
</div>
<div className="btn-group">
<button
type="button"
preventdefault:click
className="btn todo-cancel"
onClick$={() => {
isEditing.value = false;
}}
>
Cancel
<span className="visually-hidden">renaming {task.name}</span>
</button>
<button
type="submit"
preventdefault:click
className="btn btn__primary todo-edit"
onClick$={() => {
const updatedTasks = taskStore.tasks.map((task: any) => {
if (id === task.id) {
return {...task, name: nameStore.name}
}
return task
})
taskStore.tasks = updatedTasks;
isEditing.value = false;
}}
>
Save
<span className="visually-hidden">new name for {task.name}</span>
</button>
</div>
</form>
</>
);
フィルターを完成させる
filterButtonコンポーネントで、選択しているフィルターをStateで管理できるようにします。
filterStoreは親コンポーネントのroutes/index.tsx
でuseStoreを新しく作成します。
import { component$ } from "@builder.io/qwik";
type FilterButton = {
category: string
filterStore: any
}
export default component$(({ category, filterStore }: FilterButton) => {
return (
<button
type="button"
className="btn
toggle-btn"
aria-pressed="true"
preventdefault:click
onClick$={() => {
filterStore.filter = category
}}
>
<span className="visually-hidden">Show </span>
<span>{category}</span>
<span className="visually-hidden"> tasks</span>
</button>
)
})
デフォルトは全部選択中のAllにします。
const filterStore = useStore({
filter: "All"
})
次に、選択されたフィルターに応じてタスクを絞り込むための関数を用意し、taskList
に適応させていきます。
const FILTER_MAP = {
All: () => true,
Active: (task) => !task.completed,
Completed: (task) => task.completed
};
const taskList =taskStore.tasks
.filter(FILTER_MAP[filterStore.filter])
.map((task) => (
<Todo taskStore={taskStore} id={task.id} />
));
絞り込みも上手く動きました!
All | Active | Completed |
---|---|---|
完成です!
ここまで、QwikでTODOリストを作ってきました。ReactよりAPIが整っていないのと思想が違うので関数の書き方等少し冗長なものにしてしまいました。(これが正しいかもよくわかっていない。)
Builder.ioのCTOである mhevery - sanのTODOリストのデモ実装があったので併せて載せておきます。自分の記事よりも正しいものだと思うのでこちらも参考にしてみてください。