In this tutorial, we will create to do app list in react with material ui (mui 5). We will see to-do app with useState hook, to-do app using typescript example with react material UI 5.
Install & Setup Vite + React + Typescript + MUI 5
React Material UI 5 To Do App Example
1. react mui 5 simple to-do app using useState hook. You can also refactor code by split code page like TodoApp.js TodoForm.js, TodoList.js.
import { useState } from 'react';
import { Box, Paper, TextField, Checkbox, Typography, Button } from '@mui/material';
const TodoItem = ({ todo, onToggle, onDelete }) => {
const { id, text, completed } = todo;
return (
<Box display="flex" alignItems="center" marginY="8px">
<Checkbox checked={completed} onChange={() => onToggle(id)} />
<Typography variant="body1" sx={{ flexGrow: 1, mr: '16px', textDecoration: completed ? 'line-through' : 'none' }}>
{text}
</Typography>
<Button variant="outlined" size="small" color="error" onClick={() => onDelete(id)}>
Delete
</Button>
</Box>
);
};
const TodoList = ({ todos, onToggle, onDelete }) => (
<Box>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={onToggle} onDelete={onDelete} />
))}
</Box>
);
const TodoForm = ({ onSubmit }) => {
const [text, setText] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
if (!text.trim()) return;
onSubmit(text);
setText('');
};
return (
<Box
component="form"
onSubmit={handleSubmit}
display="flex"
alignItems="center"
marginBottom="16px"
sx={{ '& .MuiTextField-root': { flexGrow: 1, marginRight: '16px' } }}
>
<TextField value={text} onChange={(event) => setText(event.target.value)} label="New todo" variant="outlined" />
<Button type="submit" variant="contained" color="primary">
Add
</Button>
</Box>
);
};
const TodoApp = () => {
const [todos, setTodos] = useState([]);
const handleAddTodo = (text) => {
setTodos([...todos, { id: Date.now(), text, completed: false }]);
};
const handleToggleTodo = (id) => {
setTodos((todos) =>
todos.map((todo) =>
todo.id === id
? {
...todo,
completed: !todo.completed,
}
: todo
)
);
};
const handleDeleteTodo = (id) => {
setTodos((todos) => todos.filter((todo) => todo.id !== id));
};
return (
<Box maxWidth="600px" margin="0 auto" padding="16px">
<Typography variant="h3" align="center" gutterBottom>
Todo List
</Typography>
<Paper component={TodoForm} onSubmit={handleAddTodo} marginBottom="16px" />
<Paper component={TodoList} todos={todos} onToggle={handleToggleTodo} onDelete={handleDeleteTodo} />
</Box>
);
};
export default TodoApp;
2. react mui 5 to-do app with typescript using useState hook.
import { useState } from "react";
import {
Box,
Paper,
TextField,
Checkbox,
Typography,
Button,
} from "@mui/material";
type Todo = {
id: number;
text: string;
completed: boolean;
};
type TodoItemProps = {
todo: Todo;
onToggle: (id: number) => void;
onDelete: (id: number) => void;
};
const TodoItem = ({ todo, onToggle, onDelete }: TodoItemProps) => {
const { id, text, completed } = todo;
return (
<Box display="flex" alignItems="center" marginY="8px">
<Checkbox checked={completed} onChange={() => onToggle(id)} />
<Typography
variant="body1"
sx={{
flexGrow: 1,
mr: "16px",
textDecoration: completed ? "line-through" : "none",
}}
>
{text}
</Typography>
<Button
variant="outlined"
size="small"
color="error"
onClick={() => onDelete(id)}
>
Delete
</Button>
</Box>
);
};
type TodoListProps = {
todos: Todo[];
onToggle: (id: number) => void;
onDelete: (id: number) => void;
};
const TodoList = ({ todos, onToggle, onDelete }: TodoListProps) => (
<Box>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</Box>
);
type TodoFormProps = {
onSubmit: (text: string) => void;
};
const TodoForm = ({ onSubmit }: TodoFormProps) => {
const [text, setText] = useState("");
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!text.trim()) return;
onSubmit(text);
setText("");
};
return (
<Box
component="form"
onSubmit={handleSubmit}
display="flex"
alignItems="center"
marginBottom="16px"
sx={{ "& .MuiTextField-root": { flexGrow: 1, marginRight: "16px" } }}
>
<TextField
value={text}
onChange={(event) => setText(event.target.value)}
label="New todo"
variant="outlined"
/>
<Button type="submit" variant="contained" color="primary">
Add
</Button>
</Box>
);
};
const TodoApp = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const handleAddTodo = (text: string) => {
setTodos([...todos, { id: Date.now(), text, completed: false }]);
};
const handleToggleTodo = (id: number) => {
setTodos((todos) =>
todos.map((todo) =>
todo.id === id
? {
...todo,
completed: !todo.completed,
}
: todo
)
);
};
const handleDeleteTodo = (id: number) => {
setTodos((todos) => todos.filter((todo) => todo.id !== id));
};
return (
<Box maxWidth="600px" margin="0 auto" padding="16px">
<Typography variant="h4" align="center" gutterBottom>
React + MUI 5 + TypeScript Todo List
</Typography>
<Paper
component={TodoForm}
onSubmit={handleAddTodo}
marginBottom="16px"
/>
<Paper
component={TodoList}
todos={todos}
onToggle={handleToggleTodo}
onDelete={handleDeleteTodo}
/>
</Box>
);
};
export default TodoApp;