Skip to main content

The basic UI

The app UI contains:

  • A title
  • An input to add more todo items
  • A list of todos
  • A button to remove all todo items
function AppContent() {
return (
<div>
<h1>Todo List</h1>
<TodoInput />
<ListOfTodos />
<RemoveAllButton />
</div>
);
};



Let's detail below the components: TodoInput, ListOfTodos and RemoveAllButton.

TodoInput

The TodoInput component is a simple input field that allows the user to type a new todo item, and then press Enter or click a button to add it to the list.

function TodoInput() {

const [inputText, setInputText] = useState<string>('');

const store = useStore();

async function processInput(text: string) {
let status = await store.dispatchAndWait(new AddTodoAction(text))
if (status.isCompletedOk) setInputText('');
}

return (
<div>
<input
placeholder="Type here..."
value={inputText} maxLength={50}
onChange={(e) => { setInputText(e.target.value); }}
onKeyDown={(e) => { if (e.key === "Enter") processInput(inputText); }}
/>

<button onClick={() => processInput(inputText)}>
Add
</button>

</div>
);
};

As you can see above, the processInput function is called whenever the user presses Enter or clicks the "Add" button. This function uses the useStore hook to get a reference to the store, and then dispatches an AddTodoAction with the input text.

A simplified version of the processInput could simply use the store's dispatch method:

async function processInput(text: string) {
store.dispatch(new AddTodoAction(text));
}

However, when the action succeeds, we want to clear the input field. To do this, we need to wait until the action is completed, check if it completed successfully, and then clear the input field with setInputText('').

This is why instead of dispatch we want to use the dispatchAndWait method. It returns a Promise that completes when the action is completed. If we await it we get a status of type ActionStatus that tells us if the action was successful or not.

In other words, we get the status, and if status.isCompletedOk is true we can clear the input field:

async function processInput(text: string) {
let status = await store.dispatchAndWait(new AddTodoAction(text));
if (status.isCompletedOk) setInputText('');
}

ListOfTodos

The list of todos uses the useSelect hook to get the list of todos from state.todoList.items, and then maps over them to render each todo item component.

function ListOfTodos() {
const todoItems = useSelect((state) => state.todoList.items);

return (
<div className="listOfTodos">
{todoItems.map((item, index) => (
<TodoItemComponent key={index} item={item} />
))}
</div>
);
}

TodoItemComponent

For the moment, the todo item component is just the text of the item.

function TodoItemComponent({item}: {item: TodoItem}) {
return <div>{item.text}</div>;
}

RemoveAllButton

Finally, we add a button that uses the useStore hook to get a reference to the store, and then uses it to dispatch a RemoveAllTodosAction when clicked.

function RemoveAllButton() {
const store = useStore();
return (
<button onClick={() => store.dispatch(new RemoveAllTodosAction())}>
Remove All
</button>
);
}

Try it yourself

Type "Buy milk" in the input, and press the Add button or the Enter key. Try adding other todo items. Then remove all of them by clicking the Remove All button.

To see all files in this project, click the "hamburger icon" in the top left corner of the code editor. The state classes are in file State.ts, while the store, actions and components are in the App.tsx file.