Introduction
Todo app is a simple web app that can get you started with React. React makes it easy to accomplish our task.
Prerequisites
Basic knowledge of React.
Basic knowledge of React Hooks.
Basic knowledge of HTML, CSS, and JavaScript.
Let's start by Creating a React app with the following command. For this example, we will be using the Vite to create the Todo React app
npm create vite@latest todo --tempalate react-swc-ts
Navigate into the directory and install the dependencies.
cd todo && npm i && npm run dev
Now we can see the starter for a Vite React project. Edit the src/App.tsx page to get started.
Initially, create a component for the Input so that we can reuse it across the App.
//components/Input/Input.tsx
import { ComponentPropsWithoutRef } from "react";
import "./styles.css";
interface IInput extends ComponentPropsWithoutRef<"fieldset"> {
inputProps: ComponentPropsWithoutRef<"input">;
}
export default function Input({ inputProps }: IInput) {
return (
<fieldset className="input__container">
<label htmlFor="todo" className="input__label">
Add Todo
</label>
<input
{...inputProps}
type="text"
name="todo"
id="todo"
className="input__input"
/>
</fieldset>
);
}
Add the styles for it.
//components/Input/styles.css
.input__container {
display: flex;
flex-direction: column;
gap: 0.25rem;
border: none;
}
.input__label {
font-size: 1rem;
font-weight: 700;
}
.input__input {
padding: 0.5rem 1rem;
border-radius: 1rem;
border: 1px solid rgba(0, 0, 0, 0.4);
outline: none;
}
Clear everything in the App.tsx and get started with the code below.
//src/App.tsx
<main className="container">
<h1 className="title">Todos</h1>
</main>
We can now add a form to our UI to begin adding todos to our application.
<form onSubmit={handleAddTodo} className="add__todo__form">
<Input inputProps={{ name: "todo", value: todo, onChange: handleOnChange }} />
<button type="submit" className="add__todo__button">
Add
</button>
</form>
To update the todos we need to listen for changes in our input. We can save our values from the input in a useState React hook.
const [todo, setTodo] = useState("");
Similarly, add the onChange function to add our todo.
const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setTodo(value);
};
In this function, we listen for changes in the input and append the resulting value to our todo state using setTodo.
Similarly, the form will be submitted to the handleAddTodo function. Here, we prevent the default behavior of the browser and add todos to our setTodos useState hook.
const [todos, setTodos] = useState<{ title: string; completed: boolean }[]>([]);
const handleAddTodo = (e: SyntheticEvent) => {
e.preventDefault();
setTodos((prev) => [...prev, { title: todo, completed: false }]);
setTodo("");
};
Continue by building tabs to view the remaining and completed todos. We can handle the state of our active tab by again using the useState React hook.
const [activeTab, setActiveTab] = useState("Remaining Todos");
We can update the active tab value by listening to a click event in the tabs.
<div className="tabs__container">
{["Remaining Todos", "Completed Todos"].map((tab) => (
<button
key={tab}
className={`tabs__tab ${
activeTab === tab ? "tabs__tab__active" : ""
}`}
onClick={() => setActiveTab(tab)}
>
{tab}{" "}
{tab === "Remaining Todos"
? remainingTodos.length > 0 && `(${remainingTodos.length})`
: completedTodos.length > 0 && `(${completedTodos.length})`}
</button>
))}
</div>
In the above onClick function we make use of the setActiveTab hook to update our activeTab value.
Now we can memoize our remaining and completed todos.
const remainingTodos = useMemo(() => {
return todos.filter((todo) => !todo.completed);
}, [todos]);
const completedTodos = useMemo(() => {
return todos.filter((todo) => todo.completed);
}, [todos]);
Finally, render the list of our todos to the UI. Before, rendering the UI we must also check for the active tab state to get the active tab todos.
<ul className="todos__container">
{(activeTab === "Remaining Todos"
? remainingTodos
: completedTodos
).map((todo) => (
<li key={todo.title} className="todo__list__item">
<span className="todo__list__item__input__container">
<input
type="checkbox"
checked={todo.completed}
name={todo.title}
onChange={(e) => {
setTodos((prev) =>
prev.map((t) =>
t.title === todo.title
? { ...t, completed: e.target.checked }
: t
)
);
}}
/>
{todo.title}
</span>
<DeleteB
onClick={() => {
setTodos((prev) => prev.filter((t) => t.title !== todo.title));
}}
/>
</li>
))}
</ul>
Add styles to the page.
//src/index.css
html,
body,
:root,
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.title {
font-size: 2.5rem;
font-weight: 900;
}
.add__todo__form {
display: flex;
align-items: flex-end;
gap: 1rem;
}
.add__todo__button {
padding: 0.5rem 1rem;
border-radius: 1.5rem;
border: none;
outline: none;
}
.container {
width: 25rem;
position: absolute;
top: 25%;
left: calc(50% - 10rem);
display: flex;
flex-direction: column;
gap: 1.25rem;
padding: 2rem 2.5rem;
border-radius: 2rem;
border: 1px solid rgb(58, 40, 216);
}
.todo__list__item {
list-style-type: none;
display: flex;
gap: 0.25rem;
align-items: center;
justify-content: space-between;
padding: 0.25rem 1rem;
}
.tabs__container {
display: flex;
align-items: center;
gap: 1rem;
}
.tabs__tab {
white-space: nowrap;
font-size: 1rem;
font-weight: 500;
outline: none;
border: none;
background: transparent;
}
.tabs__tab__active {
border-bottom: 1px solid blue;
}
.todos__container {
height: 20rem;
overflow-y: scroll;
}
.todo__list__item__input__container {
display: flex;
gap: 0.25rem;
align-items: center;
}
Conclusion
Here, we created a to-do app in React using Vite and React hooks. You can now further update the UI and add additional functionalities to your to-do app to make it even better.