Todolist Example
Create Todolist project
We will create a simple todolist app where the user enters tasks and all tasks are listed in a table.
- Create a new React App using Vite (Select React and JavaScript).
- Create a new file called TodoList.jsx inside the src folder. Add the following code into the file.
function TodoList() {
return(
<></>
);
}
export default TodoList;
- The todo item has one description field called
desc
. - We need one state for the description and one array state for all todos. Let’s declare states using the
useState
hook function.
// Import useState from react
import { useState } from "react";
// Declare states
const [desc, setDesc] = useState("");
const [todos, setTodos] = useState([]);
- The
input
element is used to collect data from a user - The
addTodo
function is invoked when thebutton
is pressed
return(
<>
<input placeholder="Description" onChange={handleChange} value={desc} />
<button onClick={addTodo}>Add</button>
</>
);
- The
addTodo
function adds a new todo item to thetodos
array state. We use spread notation (…
) to add a new item at the end of the existing array. - The
handleChange
function store entered data to thedesc
state.
const handleChange = (event) => {
setDesc(event.target.value);
};
// Remember to call preventDefault() if using form
const addTodo = () => {
setTodos([...todos, desc]);
};
Display todos
- Next, we add the
table
element to thereturn
statement and display all todos inside the table using themap()
function.
return (
<>
<input placeholder="Description" onChange={handleChange} value={desc} />
<button onClick={addTodo}>Add</button>
<table>
<tbody>
{todos.map((todo, index) => (
<tr key={index}>
<td>{todo}</td>
</tr>
))}
</tbody>
</table>
</>
);
- You also have to export the
TodoList
component.
export default TodoList;
- Finally, we render the
TodoList
component inside theApp
component'sreturn
statement
import "./App.css";
import TodoList from "./TodoList";
function App() {
return (
<>
<TodoList />
</>
);
}
export default App;
- Now, you can run the project using the following npm command:
npm run dev
- Type some todos and see that they are displayed in the table.
Styling
- Styles are defined in the App.css and index.css files. These are Vite's default styles, and you can use your own instead.
- For example, the button style can be found in the index.css file
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
- The todolist is currently centered on the screen due to the body style setting
place-items: center;
. To move the todolist to the top of the screen, change this value toflex-start
in theindex.css
file.
Now, your todolist should look the following:
Try to implement the following features to your Todolist app.
- Clear the input element after the Add button is pressed.
- Add validation that an
alert
is shown if thedesc
state value is empty when new todo is added..
You can also add some styling to your table. For example, add the following styling to the App.css
file:
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
table th,
table td {
border: 1px solid black;
padding: 6px;
}
Split components
- Let's refactor the todolist example application by breaking it into multiple components. We'll use the todo item from the assignment, which also includes a due date.
- We will add a new stateless component called
TodoTable
and separate it from theTodoList
component. - Add a new file called TodoTable.jsx into the src folder. The starter code of the component is shown below.
import React from "react";
function TodoTable(props) {
return <></>;
}
export default TodoTable;
The TodoTable
component will be a stateless component. The stateless components are easy to test because they are pure functions. They are also simple to understand because they are just functions that takes props as input and returns JSX.
- Finally, import the
TodoTable
component intoTodoList
component.
import TodoTable from "./TodoTable";
- Remove the HTML table element from the
TodoList
component'sreturn
statement and add theTodoTable
component there.
return (
<>
<input type="text" onChange={handleChange} value={desc} />
<button onClick={addTodo}>Add</button>
<TodoTable />
</>
);
In this phase, our React app's component tree is the following:
-
React dataflow is one-way from top to bottom in the component tree.
-
The
TodoTable
component will be a child component of theTodoList
component. Therefore, we can send data from theTodoList
toTodoTable
component by using the props. -
Now, we can pass the
todos
state to theTodoTable
component using props.
<TodoTable todos={todos} />
- Finally, we use
map
to render todos in theTodoTable
component.
return (
<table>
<thead>
<tr>
<th>Description</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{props.todos.map((item, index) => (
<tr key={index}>
<td>{item.description}</td>
<td>{item.date}</td>
</tr>
))}
</tbody>
</table>
);
Vite and ESLint
-
Linters in programming are tools designed to analyze source code and identify potential issues, coding style violations, and error
-
ESLint is popular linter for JavaScript and TypeScript. Vite is using ESLint by default.
-
You can find the ESLint configuration file
eslint.config.cjs
from the root folder of your Vite project. You can define ESLint rules in this file to specify coding standards and guidelines for your project. -
You might have seen that ESLint is giving a warning about missing
PropTypes
. We introduced PropTypes at the beginning of the course, but we haven't used them. React recommends using TypeScript instead of checking prop types at runtime. -
You can exclude the PropTypes check by adding the highlighted line in your
.eslint.config.cjs
file:
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'react/prop-types': 0
},
},
]