Wrapping Up
We have learned almost all the concepts needed to build Engine apps.
Let's wrap up by finishing our Todos app. Three last UI elements that left putting life in are:
- Clearing completed Todos
- Toggling status of all Todos
- Removing a todo item when "X" button in
Todo
is clicked
All these operations will follow the principles seen so far in the tutorial. A
view
will accept user's input, and store it in view agnostic manner in the
global state, a producer
will get triggered and perform the actual operation.
Clearing completed Todos
Since removing Todo's is a new operation (i.e isn't an instance of deriving our state to shape it differently), the producer responsible for it is kept closer to the view that will trigger the producer.
In src/Footer.tsx
, add a click event listener for "Clear Completed" todos
button. The view is going to use state as a communication channel to trigger the
producer (as discussed in last
chapter).
const Footer: view = ({
pendingCount = observe.pendingCount,
filter = observe.filter,
updateFilter = update.filter,
+ updateClearRequest = update.clearRequest
}) => (
...
- <button className="clear-completed">Clear completed</button>
+ <button
+ className="clear-completed"
+ onClick={() => updateClearRequest.set(new Date().getTime())}
+ >
Clear completed
</button>
A new path has been introduced in the state (clearRequest
), which will contain
a value when a clear operation need to be performed. In same file (i.e
src/Footer.tsx
), add a new producer which performs the actual operation of
clearing completed todos.
const handleClearRequest: producer = ({
clearRequest = observe.clearRequest,
updateClearRequest = update.clearRequest,
getTodosById = get.todosById,
updateTodosById = update.todosById,
}) => {
if (!clearRequest) {
return;
}
const todosById = getTodosById.value();
const nextTodos = Object.values(todosById)
.filter((todo: any) => todo.status !== TodoStatuses.done)
.reduce((accum: any, todo: any) => {
accum[todo.id] = todo;
return accum;
}, {});
updateTodosById.set(nextTodos);
updateClearRequest.set(null);
};
Add it to Footer
's producers:
-Footer.producers([pendingCounter]);
+Footer.producers([pendingCounter, handleClearRequest]);
Notice how handleClearRequest
producer is changing the value that acts as its
trigger, practically resetting it. This is also a common pattern in Engine apps.
Toggling status of all Todos
Similarly, to toggle status of all Todos, create a new path in state to when the "Toggle All" checkbox is clicked, and a producer that gets triggered to do the actual work.
In src/App.tsx
, store the value in state:
- const App: view = ({ todoIds = observe.visibleTodoIds }) => (
+ const App: view = ({
+ pendingCount = observe.pendingCount,
+ todoIds = observe.visibleTodoIds,
+ updateToggleAllRequest = update.toggleAllRequest
+ }) => (
...
- <input id="toggle-all" className="toggle-all" type="checkbox" />
+ <input
+ id="toggle-all"
+ className="toggle-all"
+ type="checkbox"
+ checked={pendingCount === 0}
+ onChange={() => updateToggleAllRequest.set(new Date().getTime())}
+ />
Add a new producer handleToggleAllRequest
:
const handleToggleAllRequest: producer = ({
toggleAllRequest = observe.toggleAllRequest,
updateToggleAllRequest = update.toggleAllRequest,
getTodosById = get.todosById,
getPendingCount = get.pendingCount,
updateTodosById = update.todosById,
}) => {
if (!toggleAllRequest) {
return;
}
const todosById = getTodosById.value() as TodosById;
const pendingCount = getPendingCount.value();
const nextTodos = Object.values(todosById)
.map((todo) => {
return {
...todo,
status: pendingCount !== 0 ? TodoStatuses.done : TodoStatuses.pending,
};
})
.reduce((accum, todo) => {
accum[todo.id] = todo;
return accum;
}, {} as TodosById);
updateTodosById.set(nextTodos);
updateToggleAllRequest.set(null);
};
Add it to App
's producers:
- App.producers([syncVisibleTodoIds]);
+ App.producers([syncVisibleTodoIds, handleToggleAllRequest]);
Removing Todos
Check out the documentation for update, and try to implement this feature by yourself 🙂
Solution
In src/Todo/View.tsx
,
- <button className="destroy" />
+ <button className="destroy" onClick={() => updateTodo.remove()} />