Welcome to our comprehensive tutorial on building a Todo List App using JavaScript!
This step-by-step guide will walk you through creating a functional and stylish todo list application.
By the end of this tutorial, you’ll have gained valuable insights into JavaScript programming and DOM manipulation, empowering you to create your own dynamic web applications.
Let’s dive in and start building your to-do list from scratch!”
Understanding JavaScript and its role in creating a todo list app
JavaScript is a versatile and powerful programming language that allows developers to create dynamic and interactive web applications.
It is the go-to for front-end development, enabling the creation of engaging user interfaces and enhancing the overall user experience.
In the context of creating a Todo List app, JavaScript plays a crucial role in implementing the functionality of adding, deleting, and manipulating tasks.
With JavaScript, you can easily handle user interactions, update the app’s state in real time, and dynamically apply changes to the user interface.
To start with JavaScript, you need a good understanding of its syntax, data types, and control flow. Additionally, knowledge of HTML and CSS is beneficial as they work together with JavaScript to create a fully functional web application.
Setting up the development environment
In this tutorial, we are going to use VS Code, feel free to use whatever IDE of your choice. We will also be building the todo app using Reactjs. So make sure Nodejs is installed in your system.
VS code is our text editor, Nodejs is the cross-platform JavaScript runtime environment, and Reactjs is a JavaScript library that will make our life much easier when developing the app.
Creating the basic structure and layout of the todo list app
To summarize, we need a text box, a check box, a delete icon, a button to save items, and an area where we would like to display the items on the list.
To use the power of Reactjs, we will split all these into components like the following.
<TodoContainer> // Wrapper (orange part on the image above)
<List/> // Display items (blue part)
<TextField/> // Text input field (black part)
<Button/> // Save button (green part)
</TodoContainer>
Before writing code, let’s create the app using the Vite create command. I will be using NPM; you can also use pnpm or Yarn.
npm create vite@latest
You will be asked to enter your app name. Let’s do with todo-app
Press enter when you are done typing the app name. Then select React
as the template.
then select typeScript
And we are ready to start coding.
Navigate inside the folder todo-app, and run the command as instructed.
Once you do that, this should be somewhat the screen you have
Open your browser and type http://localhost:5173
The screen should look like the following
Before we start, I would like to change the port at which the app is served. I like the port 3000
. Let’s ask Vite to serve the app on the port 3000
instead of the default port 5173
.
// vite.config.ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: { port: 3000 },
})
Now, we can start creating our components.
Here is what the simple version should look like
// List.tsx
export const List = ({ list }: { list: string[] }) => {
return list.map((el, index) => <div key={index}>{el}</div>)
}
// TextField.tsx
export const TextField = ({
state,
setState,
}: {
state: string
setState: (state: string) => void
}) => {
return <input value={state} onChange={(e) => setState(e.target.value)} />
}
// TodoContainer.tsx
import { ReactNode } from "react"
export const TodoContainer = ({ children }: { children: ReactNode }) => {
return <div>{children}</div>
}
// App.tsx
import { useState } from "react"
import { TodoContainer } from "./components/TodoContainer"
import { List } from "./components/List"
import { TextField } from "./components/TextField"
import "./App.css"
function App() {
const [input, setInput] = useState<string>("")
const [list, setList] = useState<string[]>([""])
const handleSave = () => {
const newList = [...list, input]
setList(newList)
setInput("")
}
return (
<div className="card">
<TodoContainer>
<List list={list} />
<TextField state={input} setState={setInput} />
<button onClick={handleSave}>Save</button>
</TodoContainer>
</div>
)
}
export default App
Now, we can add tasks to the list. That is all about it. But the app looks very bad.
Now, let’s add the functionality to delete an item from the list.
For this, we need to alter the type of list
. The List
component becomes
// List.tsx
import { Trash } from "./Trash"
export type ListProp = {
content: string
id: number
}
export const List = ({
list,
setList,
}: {
list: ListProp[]
setList: (arg: ListProp[]) => void
}) => {
return (
<div>
{list.map((el) => (
<div key={el.id}>
{el.content}
<Trash setList={setList} list={list} index={el.id} />
</div>
))}
</div>
)
}
We added the trash icon, here is its content
// Trash
import { ListProp } from "./List"
export const Trash = ({
list,
setList,
index,
}: {
list: ListProp[]
setList: (arg: ListProp[]) => void
index: number
}) => {
function handleDelete() {
const newList = list.filter((element) => element.id !== index)
setList(newList)
}
return (
<button onClick={handleDelete}>
<svg aria-hidden="true" fill="none" width={20} height={20} color="red">
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1 1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"
/>
</svg>
</button>
)
}
We need to adjust the App.tsx
file
// App.tsx
import { useState } from "react"
import { TodoContainer } from "./components/TodoContainer"
import { List, ListProp } from "./components/List"
import { TextField } from "./components/TextField"
import "./App.css"
function App() {
const [input, setInput] = useState<string>("")
const [list, setList] = useState<ListProp[]>([])
const handleSave = () => {
const newList = [...list, { content: input, id: Math.random() }]
setList(newList)
setInput("")
}
return (
<>
<div className="card">
<TodoContainer>
<List list={list} setList={setList} />
<TextField state={input} setState={setInput} />
<button onClick={handleSave}>Save</button>
</TodoContainer>
</div>
</>
)
}
export default App
We added one more property to the data structure of list
. Now, each item on our list has an ID that allows us to target it when we want to remove it from the list.
Now, we are able to add and delete items from the list.
Implementing features: marking tasks as complete
Let’s add the CheckBox
component
// CheckBox.tsx
export const CheckBox = ({
isComplete,
onChange,
}: {
isComplete: boolean
onChange: (arg: boolean) => void
}) => {
return (
<input
type="checkbox"
checked={isComplete}
onChange={(e) => onChange(e.target.checked)}
/>
)
}
The List
component becomes
// List.tsx
import { CheckBox } from "./CheckBox"
import { Trash } from "./Trash"
export type ListProp = {
content: string
id: number
isComplete: boolean
}
export const List = ({
list,
setList,
}: {
list: ListProp[]
setList: (arg: ListProp[]) => void
}) => {
function handleChange(currentItem: ListProp) {
const newList = {
id: currentItem.id,
content: currentItem.content,
isComplete: !currentItem.isComplete,
}
const alteredList = list.map((el) => {
if (el.id === currentItem.id) {
return newList
}
return el
})
setList(alteredList)
}
return (
<div>
{list.map((el) => (
<div key={el.id}>
{el.content}
<CheckBox
isComplete={el.isComplete}
onChange={() => handleChange(el)}
/>
<Trash setList={setList} list={list} index={el.id} />
</div>
))}
</div>
)
}
And the App.tsx
becomes
// App.tsx
import { useState } from "react"
import { TodoContainer } from "./components/TodoContainer"
import { List, ListProp } from "./components/List"
import { TextField } from "./components/TextField"
import "./App.css"
function App() {
const [input, setInput] = useState<string>("")
const [list, setList] = useState<ListProp[]>([])
const handleSave = () => {
const newList = [
...list,
{ content: input, id: Math.random(), isComplete: false },
]
setList(newList)
setInput("")
}
return (
<div className="card">
<TodoContainer>
<List list={list} setList={setList} />
<TextField state={input} setState={setInput} />
<button onClick={handleSave}>Save</button>
</TodoContainer>
</div>
)
}
export default App
Styling the todo list app to enhance user experience
Let’s add a few styles to the app to enhance the user experience
We will adjust the App.css
file and add the CSS
classes whenever needed. Check this file for the changes.
Conclusion and next steps for further enhancing the todo list app.
We’ve successfully developed a to-do app using ReactJS, but we made some compromises and shortcuts in the process. While we take pride in our achievement, we recognize the importance of addressing areas that could be enhanced or improved.
Currently, we utilize Math.random()
to generate IDs when adding new items to the list. While it’s unlikely, there’s a potential risk of having two items with the same ID, introducing a bug into the app. To mitigate this possibility, it would be advisable to replace Math.random()
with a more suitable method for generating unique IDs.
Currently, to add an item to the list, we’re required to click the Save button. A notable enhancement would be to enable adding items using the enter key on the keyboard, providing a more streamlined and user-friendly experience.
When you reload the page, the app loses its current state. It would be beneficial to save the to-do list in the local storage, allowing you to refresh the page without losing the previously entered to-dos.
Feel free to check the whole code on GitHub and the live version of the app here.
Feel invited to check how I deployed this React app to Github Pages.