I am going to make simple todo list app by using ReactTS. It has feature CRUD and all todos are saved to localstorage. So, I need eaxmple by using ReactTS and Radix for ui.
1. Create a new React project.
npx create-react-app todo-list
2. Add Radix to your project.
npm install @radix-ui/react
3. Create a new file called src/TodoItem.tsx
.
import * as React from 'react'
import {Radix} from '@radix-ui/react'
import {CheckIcon, TrashIcon} from '@radix-ui/react-icons'
export interface TodoItemProps {
todo: {
id: string
text: string
completed: boolean
}
onToggle: (id: string) => void
onDelete: (id: string) => void
}
const TodoItem: React.FC<TodoItemProps> = ({todo, onToggle, onDelete}) => {
return (
<Radix.Div className="flex items-center justify-between p-4 bg-gray-100 rounded-md shadow-sm">
<label className="flex items-center">
<Radix.Checkbox checked={todo.completed} onChange={(e) => onToggle(todo.id)}>
<CheckIcon className="w-6 h-6 text-gray-500" />
</Radix.Checkbox>
<span className="ml-2 text-gray-700">{todo.text}</span>
</label>
<Radix.Button onClick={() => onDelete(todo.id)}>
<TrashIcon className="w-6 h-6 text-gray-500" />
</Radix.Button>
</Radix.Div>
)
}
export default TodoItem
4. Create a new file called src/TodoList.tsx
.
import * as React from 'react'
import {Radix} from '@radix-ui/react'
import {PlusIcon} from '@radix-ui/react-icons'
import TodoItem from './TodoItem'
export interface TodoListProps {
todos: {
id: string
text: string
completed: boolean
}[]
onToggle: (id: string) => void
onDelete: (id: string) => void
onAdd: (text: string) => void
}
const TodoList: React.FC<TodoListProps> = ({todos, onToggle, onDelete, onAdd}) => {
const [newTodoText, setNewTodoText] = React.useState('')
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
onAdd(newTodoText)
setNewTodoText('')
}
return (
<div className="max-w-md mx-auto">
<h1 className="text-3xl font-bold mb-4">Todo List</h1>
<form onSubmit={handleSubmit} className="flex items-center mb-4">
<input
type="text"
placeholder="Add a new todo"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
className="flex-1 p-2 border border-gray-300 rounded-md shadow-sm"
/>
<Radix.Button type="submit" className="ml-2">
<PlusIcon className="w-6 h-6 text-gray-500" />
</Radix.Button>
</form>
<ul className="list-none">
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={onToggle} onDelete={onDelete} />
))}
</ul>
</div>
)
}
export default TodoList
5. Create a new file called src/App.tsx
.
import * as React from 'react'
import TodoList from './TodoList'
const App: React.FC = () => {
const [todos, setTodos] = React.useState<
{
id: string
text: string
completed: boolean
}[]
>([])
const onToggle = (id: string) => {
setTodos((prevTodos) => {
return prevTodos.map((todo) => {
if (todo.id === id) {
return {...todo, completed: !todo.completed}
}
return todo
})
})
}
const onDelete = (id: string) => {
setTodos((prevTodos) => {
return prevTodos.filter((todo) => todo.id !== id)
})
}
const onAdd = (text: string) => {
setTodos((prevTodos) => {
return [
...prevTodos,
{
id: Math.random().toString(),
text,
completed: false,
},
]
})
}
return (
<div className="App">
<TodoList todos={todos} onToggle={onToggle} onDelete={onDelete} onAdd={onAdd} />
</div>
)
}
export default App
6. Start the development server.
npm start
7. Open your browser to http://localhost:3000
.
You should see a todo list app that lets you add, delete, and toggle the completion status of todos.
1. Create a New React Project
npx create-react-app todo-app --template @typescript
2. Install Radix UI
npm install @radix-ui/react
3. Create a New Component for the Todo Item
// TodoItem.tsx
import { useState } from "react";
import { Button, Checkbox, Container } from "@radix-ui/react";
export const TodoItem = ({ todo, onDelete, onToggle }) => {
const [checked, setChecked] = useState(todo.completed);
const handleToggle = () => {
setChecked(!checked);
onToggle(todo.id);
};
const handleDelete = () => {
onDelete(todo.id);
};
return (
<Container>
<Checkbox checked={checked} onChange={handleToggle} />
<Container>
{todo.text}
</Container>
<Button onClick={handleDelete}>Delete</Button>
</Container>
);
};
4. Create a New Component for the Todo List
// TodoList.tsx
import { useEffect, useState } from "react";
import { TodoItem } from "./TodoItem";
export const TodoList = () => {
const [todos, setTodos] = useState<Todo[]>([]);
useEffect(() => {
const storedTodos = localStorage.getItem("todos");
if (storedTodos) {
const parsedTodos: Todo[] = JSON.parse(storedTodos);
setTodos(parsedTodos);
}
}, []);
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
const addTodo = (text: string) => {
const newTodo: Todo = {
id: Date.now(),
text: text,
completed: false,
};
setTodos([...todos, newTodo]);
};
const toggleTodo = (id: number) => {
const updatedTodos = todos.map((todo) => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed,
};
}
return todo;
});
setTodos(updatedTodos);
};
const deleteTodo = (id: number) => {
const filteredTodos = todos.filter((todo) => todo.id !== id);
setTodos(filteredTodos);
};
return (
<Container>
<form onSubmit={(e) => {
e.preventDefault();
const text = (e.target as HTMLFormElement).querySelector(
'input[name="todo"]'
) as HTMLInputElement;
addTodo(text.value);
text.value = "";
}}>
<label htmlFor="todo">Add a new todo:</label>
<input type="text" name="todo" />
<button type="submit">Add</button>
</form>
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={deleteTodo}
onToggle={toggleTodo}
/>
))}
</ul>
</Container>
);
};
5. Render the Todo List in the App Component
import { TodoList } from "./TodoList";
const App = () => {
return (
<div>
<h1>Todo List</h1>
<TodoList />
</div>
);
};
export default App;
6. Start the Development Server
npm start
7. Open your browser to http://localhost:3000
You should see a simple todo list app with a form for adding new todos and a list of existing todos. You can check off todos as you complete them and delete them by clicking the "Delete" button.
8. Additional Features
You can add additional features to your todo list app, such as:
* Drag and drop to reorder todos
* Dark mode
* Notifications for new todos
* Integration with a backend API
The possibilities are endless!
1. Create a new React application:
npx create-react-app todo-app-reactts
2. Install the necessary dependencies:
npm install @radix-ui/react @radix-ui/alpha radix reset-css
3. Create a new file called src/App.tsx
:
import React, { useState } from 'react';
import {
List,
ListItem,
ListItemButton,
ListItemContent,
ListItemText,
Input,
Button,
Box,
} from '@radix-ui/react';
import { CloseIcon } from '@radix-ui/alpha';
const App = () => {
const [todos, setTodos] = useState<string[]>([]);
const [todo, setTodo] = useState('');
const addTodo = () => {
if (todo.trim()) {
setTodos([...todos, todo]);
setTodo('');
}
};
const deleteTodo = (index: number) => {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
};
return (
<Box p={20}>
<h1>Todo List</h1>
<Input
id="todo"
value={todo}
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new todo..."
/>
<Button onClick={addTodo}>Add Todo</Button>
<List>
{todos.map((todo, index) => (
<ListItem key={index}>
<ListItemButton onClick={() => deleteTodo(index)}>
<CloseIcon />
</ListItemButton>
<ListItemContent>
<ListItemText>{todo}</ListItemText>
</ListItemContent>
</ListItem>
))}
</List>
</Box>
);
};
export default App;
4. Create a new file called src/index.css
:
/* reset.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
height: 100%;
font-family: sans-serif;
}
a {
text-decoration: none;
color: inherit;
}
ul {
list-style-type: none;
}
button {
cursor: pointer;
}
/* app.css */
body {
background-color: #f5f5f5;
}
h1 {
margin-bottom: 20px;
}
input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-bottom: 10px;
}
button {
padding: 10px 20px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #555;
color: #fff;
}
ul {
padding: 0;
}
li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #ccc;
}
li:last-child {
border-bottom: none;
}
.close-icon {
cursor: pointer;
}
5. Start the development server:
npm start
6. Open your browser and navigate to http://localhost:3000
to see your todo list app in action!
1. Create a new React app.
npx create-react-app todo-app
2. Install the necessary dependencies.
npm install radix-ui
3. Create a new component for the todo list.
src/components/TodoList.tsx
import { useState, useEffect } from "react";
import { List, Item, Button } from "radix-ui";
const TodoList = () => {
const [todos, setTodos] = useState<string[]>([]);
useEffect(() => {
const storedTodos = localStorage.getItem("todos");
if (storedTodos) {
setTodos(JSON.parse(storedTodos));
}
}, []);
const addTodo = (todo: string) => {
setTodos([...todos, todo]);
localStorage.setItem("todos", JSON.stringify([...todos, todo]));
};
const deleteTodo = (index: number) => {
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
localStorage.setItem("todos", JSON.stringify(newTodos));
};
return (
<List>
{todos.map((todo, index) => (
<Item key={index}>
<div>{todo}</div>
<Button onClick={() => deleteTodo(index)}>Delete</Button>
</Item>
))}
<Button onClick={() => addTodo("New Todo")}>Add Todo</Button>
</List>
);
};
export default TodoList;
5. Start the development server.
npm start
6. Open your browser to http://localhost:3000
to see the todo list app.
1. Setup Project:
- Install React with TypeScript: npx create-react-app my-todo-app --template @typescript
- Install Radix: yarn add @radix-ui/react @radix-ui/styled-components
2. Create Todo Context:
- Create a todoContext.ts
file in src
folder.
- Define Todo state and context provider:
import { createContext, useContext } from "react";
import { Todo } from "./types";
const TodoContext = createContext(null);
export const TodoContextProvider = ({ children }: { children: React.ReactNode }) => {
const [todos, setTodos] = useState([]);
const addTodo = (todo: string) => {
setTodos([...todos, { id: Date.now(), title: todo, completed: false }]);
};
const deleteTodo = (id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const toggleTodoCompleted = (id: number) => {
setTodos(
todos.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo))
);
};
return {children};
};
export const useTodos = () => useContext(TodoContext);
3. Define Todo List Component:
- Create a TodoList.tsx
component in src
folder.
- Import necessary hooks and components:
import { useState, useEffect } from "react";
import styled from "styled-components";
import { Todo } from "./types";
import { useTodos } from "./todoContext";
- Define UI and logic:
const TodoList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState<string>("");
const { todos, addTodo, deleteTodo, toggleTodoCompleted } = useTodos();
useEffect(() => {
const todosFromLocalStorage = localStorage.getItem("todos") ? JSON.parse(localStorage.getItem("todos")!) : [];
setTodos(todosFromLocalStorage);
}, []);
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos!));
}, [todos]);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
};
return (
<Wrapper>
<Header>
<h1>Todo List</h1>
<div>
<input type="text" value={searchTerm} onChange={handleSearch} placeholder="Search todos..." />
</div>
</Header>
<ul>
{todos!
.filter((todo) => todo.title.toLowerCase().includes(searchTerm.toLowerCase()))
.map((todo) => (
<li key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodoCompleted(todo.id)}
/>
<span>{todo.title}</span>
</label>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
<AddTodoForm onSubmit={(e) => {
e.preventDefault();
const newTodo = e.target.querySelector("input[name='todo']");
if (newTodo.value !== "") {
addTodo(newTodo.value);
newTodo.value = "";
}
}}>
<input type="text" name="todo" placeholder="Add a new todo..." />
<button type="submit">Add</button>
</AddTodoForm>
</Wrapper>
);
};
export default TodoList;
4. Create Styling Component:
- Create a style.ts
file in src
folder.
- Define Radix styled components:
import { styled } from "@radix-ui/styled-components";
export const Wrapper = styled("div", {
display: "flex",
flexDirection: "column",
gap: "1rem",
});
export const Header = styled("header", {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "1rem",
borderBottom: "1px solid #ddd",
});
export const AddTodoForm = styled("form", {
display: "flex",
alignItems: "center",
gap: "1rem",
padding: "1rem",
backgroundColor: "#f5f5f5",
borderRadius: "5px",
});
export const TodoList = styled("ul", {
listStyleType: "none",
display: "flex",
flexDirection: "column",
padding: 0,
gap: "1rem",
overflowY: "auto",
});
export const TodoListItem = styled("li", {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "1rem",
backgroundColor: "#fff",
borderRadius: "5px",
"&:hover": { backgroundColor: "#f5f5f5" },
});
export const TodoLabel = styled("label", {
display: "flex",
alignItems: "center",
gap: "1rem",
});
export const CompletedTodoSpan = styled("span", {
textDecoration: "line-through",
color: "#999",
opacity: 0.5,
});
export const DeleteButton = styled("button", {
padding: "0.25rem 0.5rem",
borderRadius: "5px",
backgroundColor: "#d33",
color: "#fff",
border: "none",
cursor: "pointer",
"&:hover": { backgroundColor: "#c00" },
});
5. Render App:
- In App.tsx
, import TodoList
and wrap it in TodoContextProvider
.
- Return TodoList
component as children.
6. Start the App:
- Run yarn start
to start the development server.
You'll have a fully functional React + TypeScript todo list app with CRUD operations and local storage persistence using Radix for UI.
1. Create a new React app.
npx create-react-app todo-app-ts
2. Install the required dependencies.
yarn add @radix-ui/react-dropdown-menu @radix-ui/react-input @radix-ui/react-popover @radix-ui/react-toggle radix @emotion/react @emotion/styled
3. Create a new file called src/components/TodoItem.tsx
.
import { useState } from 'react';
import {
ArrowLeftIcon,
CheckIcon,
CloseIcon,
StyledMenu,
StyledMenuItem,
StyledPopover,
styled,
} from '@radix-ui/react-dropdown-menu';
import { StyledButton, StyledCheckbox } from '@radix-ui/react-toggle';
import {
Container,
Input,
Task,
Textbox,
Title,
} from './styles';
interface TodoItemProps {
todo: {
id: string;
title: string;
completed: boolean;
};
onToggleCompleted: (id: string) => void;
onEditTodo: (id: string) => void;
onDeleteTodo: (id: string) => void;
}
const TodoItem: React.FC<TodoItemProps> = ({ todo, onToggleCompleted, onEditTodo, onDeleteTodo }) => {
const [isEditing, setIsEditing] = useState(false);
const [title, setTitle] = useState(todo.title);
const handleToggleCompleted = () => {
onToggleCompleted(todo.id);
};
const handleEditTodo = () => {
setIsEditing(true);
};
const handleDeleteTodo = () => {
onDeleteTodo(todo.id);
};
const handleSaveTodo = () => {
setIsEditing(false);
onEditTodo(todo.id, title);
};
return (
<Container>
<StyledCheckbox checked={todo.completed} onChange={handleToggleCompleted}>
<CheckIcon />
</StyledCheckbox>
{isEditing ? (
<Textbox>
<Input value={title} onChange={(e) => setTitle(e.target.value)} />
<StyledButton onClick={handleSaveTodo}>Save</StyledButton>
</Textbox>
) : (
<Task completed={todo.completed}>{todo.title}</Task>
)}
<StyledPopover>
<StyledButton asChild>
<ArrowLeftIcon />
</StyledButton>
<StyledMenu>
<StyledMenuItem onClick={handleEditTodo}>Edit</StyledMenuItem>
<StyledMenuItem onClick={handleDeleteTodo}>Delete</StyledMenuItem>
</StyledMenu>
</StyledPopover>
</Container>
);
};
export default TodoItem;
4. Create a new file called src/components/TodoList.tsx
.
import { useState } from 'react';
import TodoItem from './TodoItem';
interface Todo {
id: string;
title: string;
completed: boolean;
}
const TodoList: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const addTodo = (title: string) => {
const newTodo = {
id: Date.now().toString(),
title,
completed: false,
};
setTodos((prevTodos) => [...prevTodos, newTodo]);
};
const toggleCompleted = (id: string) => {
setTodos((prevTodos) => {
const updatedTodos = prevTodos.map((todo) => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed,
};
}
return todo;
});
return updatedTodos;
});
};
const editTodo = (id: string, title: string) => {
setTodos((prevTodos) => {
const updatedTodos = prevTodos.map((todo) => {
if (todo.id === id) {
return {
...todo,
title,
};
}
return todo;
});
return updatedTodos;
});
};
const deleteTodo = (id: string) => {
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
};
return (
<div>
<Title>Todo List</Title>
<Input placeholder="Add a new todo..." onKeyPress={(e) => { if (e.key === 'Enter') addTodo(e.target.value); }} />
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggleCompleted={toggleCompleted}
onEditTodo={editTodo}
onDeleteTodo={deleteTodo}
/>
))}
</div>
);
};
export default TodoList;
5. Create a new file called src/styles.ts
.
import styled from '@emotion/styled';
export const Container = styled.div`
display: flex;
align-items: center;
padding: 12px;
border-bottom: 1px solid #e0e0e0;
`;
export const Input = styled.input`
width: 100%;
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
`;
export const Task = styled.p`
flex-grow: 1;
margin-left: 12px;
text-decoration: ${(props: { completed: boolean }) => (props.completed ? 'line-through' : 'none')};
`;
export const Textbox = styled.div`
display: flex;
align-items: center;
width: 100%;
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
`;
export const Title = styled.h1`
margin-bottom: 32px;
`;
7. Run the app.
yarn start
8. Open your browser to http://localhost:3000
and you should see your todo list app.
1. Create a new React project.
npx create-react-app todo-app
2. Install the necessary dependencies.
npm install @radix-ui/react-listbox @radix-ui/react-checkbox @radix-ui/react-input
3. Create a new file called Todo.tsx
in the src
folder.
import { useState } from "react";
import { Listbox, ListboxOption } from "@radix-ui/react-listbox";
import { Checkbox } from "@radix-ui/react-checkbox";
import { TextInput } from "@radix-ui/react-input";
const Todo = () => {
const [todos, setTodos] = useState<string[]>([]);
const [newTodo, setNewTodo] = useState("");
const addTodo = () => {
if (newTodo !== "") {
setTodos([...todos, newTodo]);
setNewTodo("");
}
};
const removeTodo = (todo: string) => {
setTodos(todos.filter((t) => t !== todo));
};
return (
<div>
<TextInput
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new todo"
/>
<button onClick={addTodo}>Add</button>
<Listbox>
{todos.map((todo) => (
<ListboxOption key={todo} value={todo}>
{todo}
<Checkbox onClick={() => removeTodo(todo)} />
</ListboxOption>
))}
</Listbox>
</div>
);
};
export default Todo
4. Create a new file called App.tsx
in the src
folder.
import Todo from "./Todo";
const App = () => {
return (
<div>
<h1>Todo List</h1>
<Todo />
</div>
);
};
export default App;
5. Add the following code to the index.tsx
file.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
6. Start the development server.
npm start
7. Open your browser and go to http://localhost:3000
to see your todo list app.
1