Todo List
Develop a simple todo list where users can add items to a list using a text input and a button. Items should also be removable.
Solution
Click an item to remove it
- Enter a new task in the todo list
- Click a task to remove it
- Try adding a blank entry
Explanation
useState vs useRef
To keep track of the task list, you need to use the useState
hook. To access the users input you could use state but as it is just a reference to be added to your task list, useRef
will be more efficient.
Semantic HTML for accessibility
Notice that the tasks are rendered to the page inside an unordered list. For accessibility reasons, it's makes sense to add semantic HTML.
How to add a new task
Adding a new task takes the input via the inputData useRef
and runs it through handleSubmit
. After checking the data a new unique id
is added. Note how we update the todos state with a function and copy the previous state before resetting the input.
Think of the inverse approach to delete
The deleteHandler
works by filtering all values that are not equal to the task you want to remove.
Code
import { useRef, useState } from "react";
import styles from "./Solution.module.css";
import { v4 as uuid } from "uuid";
type todoProps = {
id: string;
todo: string;
};
type todosProps = todoProps[];
const Solution = () => {
const inputData = useRef<HTMLInputElement>(null);
const [todos, setTodos] = useState<todosProps | []>([
{ id: "1", todo: "Enter a new task in the todo list" },
{ id: "2", todo: "Click a task to remove it" },
{ id: "3", todo: "Try adding a blank entry" },
]);
const [error, setError] = useState(false);
function handleSubmit(): void {
const text: string | undefined = inputData?.current?.value.trim();
if (!text) {
setError(true);
return;
}
setError(false);
const task: todoProps = {
id: uuid(),
todo: text,
};
setTodos((prev) => [...prev, task]);
if (inputData.current) {
inputData.current.value = "";
}
}
function deleteHandler(id: string): void {
const newTodos: todosProps = todos.filter((item) => item.id !== id);
setTodos(newTodos);
}
return (
<div className={styles.container}>
<p>Click an item to remove it</p>
<ul className={styles.list}>
{todos &&
todos.map((todo) => {
return (
<li
key={todo.id}
className={styles.listItem}
onClick={() => deleteHandler(todo.id)}
>
{todo.todo}
</li>
);
})}
</ul>
<div className={styles.inputContainer}>
<input
className={styles.input}
type="text"
name="input"
id="input"
ref={inputData}
/>
<button onClick={handleSubmit} className={styles.button}>
Add
</button>
</div>
{error && <p className={styles.error}>Enter a value</p>}
</div>
);
};
export default Solution;
Styling
.container {
display: flex;
flex-direction: column;
gap: 10px;
}
.inputContainer {
display: flex;
gap: 10px;
}
.input {
padding: 5px;
width: 400px;
border: 1px solid rgb(189, 189, 189);
}
.button {
padding: 10px 25px;
background-color: var(--white);
border: 1px solid var(--code-background);
cursor: pointer;
transition: filter 0.3s;
}
.button:hover {
filter: brightness(0.8);
}
.input,
.button {
border-radius: 5px;
}
.title {
margin: 10px 0;
}
.list {
display: flex;
flex-direction: column;
gap: 15px;
margin: 10px 0 20px;
}
.listItem {
border: 1px dotted grey;
width: 400px;
overflow-wrap: break-word;
padding: 10px;
transition: background-color 0.3s;
}
.listItem:hover,
.listItem:active {
background-color: var(--colour-primary);
cursor: pointer;
}
.error {
color: rgb(218, 56, 56);
}