To Do App
Here's a complete Todo List application built with React, TypeScript, and Tailwind CSS, leveraging local storage for persistence.
-
React for UI Development: React is a declarative JavaScript library for building user interfaces. It promotes a component-based architecture, where UI elements are broken down into reusable, self-contained components. Key concepts include:
- Components: Functional components (using hooks) are the standard way to build UI in modern React.
- State (
useState): Manages data that can change over time within a component, triggering re-renders when updated. - Side Effects (
useEffect): Handles operations that interact with the outside world (e.g., data fetching, DOM manipulation, local storage interaction). It runs after every render by default, but its execution can be controlled by a dependency array. - Props: A mechanism for passing data from parent components to child components, enabling data flow down the component tree.
-
TypeScript for Type Safety: TypeScript is a superset of JavaScript that adds static typing. This helps catch errors during development, improves code readability, and provides better tooling support (e.g., autocompletion, refactoring). It's crucial for building robust and maintainable applications, especially as they scale.
-
Tailwind CSS for Styling: Tailwind CSS is a utility-first CSS framework. Instead of providing pre-designed components, it offers a vast set of low-level utility classes that can be composed directly in your HTML/JSX to style elements.
- Utility-First: Rapidly build custom designs without writing custom CSS.
- Purging: Tailwind CSS can remove unused CSS, resulting in highly optimized and small CSS bundles for production.
- Configuration:
tailwind.config.jsallows extensive customization of design tokens (colors, spacing, fonts, etc.).
-
Local Storage for Data Persistence:
localStorageis a web storage API that allows web applications to store data persistently in the browser.- Persistence: Data stored in
localStoragehas no expiration time and remains available even after the browser is closed. - Key-Value Pairs: Data is stored as key-value pairs, where both keys and values must be strings.
- Serialization: To store JavaScript objects or arrays, they must be converted to JSON strings using
JSON.stringify()before saving and parsed back usingJSON.parse()when retrieving. - Limitations:
localStorageis synchronous, suitable for small amounts of non-sensitive data, and not ideal for large datasets or complex queries. - Integration with React:
useEffectis commonly used to synchronize React state withlocalStorage, loading data on initial render and saving data whenever the state changes.
- Persistence: Data stored in
This project will follow a standard React application structure, typically generated by create-react-app or Vite.
todo-app/
├── public/
│ └── index.html
├── src/
│ ├── components/
│ │ ├── TodoItem.tsx
│ │ └── TodoList.tsx
│ ├── utils/
│ │ └── localStorage.ts
│ ├── types/
│ │ └── todo.ts
│ ├── App.tsx
│ ├── index.css
│ └── index.tsx
├── tailwind.config.js
├── postcss.config.js
├── tsconfig.json
├── package.json
└── README.md
public/index.html: The main HTML file that serves as the entry point for the React application.src/index.tsx: The entry point for the React application, where theAppcomponent is rendered into the DOM.src/App.tsx: The main application component. It manages the global state of todos, including adding, toggling completion, and deleting. It also handles loading and saving todos to local storage.src/components/TodoItem.tsx: A functional component responsible for rendering a single todo item. It receives props for the todo's data and callback functions for handling completion toggles and deletion.src/components/TodoList.tsx: A functional component that receives the array of todos and renders a list ofTodoItemcomponents.src/utils/localStorage.ts: Contains utility functions to interact with the browser'slocalStorageAPI, specifically for getting and setting the todo list. This centralizes local storage logic.src/types/todo.ts: Defines the TypeScript interface for aTodoobject, ensuring type safety throughout the application.src/index.css: The main CSS file where Tailwind CSS directives are imported.tailwind.config.js: Tailwind CSS configuration file, where you define paths to your source files so Tailwind can scan them for utility classes.postcss.config.js: PostCSS configuration, used by Tailwind CSS for processing CSS.tsconfig.json: TypeScript configuration file.package.json: Defines project metadata, scripts, and dependencies.
First, let's assume you've set up a React project with TypeScript and Tailwind CSS. If not, you can typically do this using Vite (recommended for new projects) or Create React App.
Using Vite (Recommended):
npm create vite@latest my-todo-app -- --template react-tscd my-todo-appnpm installnpm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p(This createstailwind.config.jsandpostcss.config.js)
Using Create React App:
npx create-react-app my-todo-app --template typescriptcd my-todo-appnpm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p
Now, let's populate the files:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Todo App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}", // Scan all JS, TS, JSX, TSX files in src
],
theme: {
extend: {},
},
plugins: [],
};module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom styles if any, but keep it minimal for utility-first approach */
body {
@apply bg-gray-100 font-sans;
}export interface Todo {
id: string;
text: string;
completed: boolean;
}import { Todo } from '../types/todo';
const LOCAL_STORAGE_KEY = 'react-todo-list';
/**
* Retrieves the list of todos from local storage.
* @returns An array of Todo objects, or an empty array if no data is found or an error occurs.
*/
export const getTodos = (): Todo[] => {
try {
const storedTodos = localStorage.getItem(LOCAL_STORAGE_KEY);
return storedTodos ? JSON.parse(storedTodos) : [];
} catch (error) {
console.error("Error retrieving todos from local storage:", error);
return [];
}
};
/**
* Saves the list of todos to local storage.
* @param todos The array of Todo objects to save.
*/
export const saveTodos = (todos: Todo[]): void => {
try {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos));
} catch (error) {
console.error("Error saving todos to local storage:", error);
}
};import React from 'react';
import { Todo } from '../types/todo';
interface TodoItemProps {
todo: Todo;
onToggleComplete: (id: string) => void;
onDelete: (id: string) => void;
}
const TodoItem: React.FC<TodoItemProps> = ({ todo, onToggleComplete, onDelete }) => {
return (
<li className="flex items-center justify-between p-4 bg-white border-b border-gray-200 last:border-b-0">
<div className="flex items-center">
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggleComplete(todo.id)}
className="mr-3 h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
<span className={`text-lg ${todo.completed ? 'line-through text-gray-500' : 'text-gray-800'}`}>
{todo.text}
</span>
</div>
<button
onClick={() => onDelete(todo.id)}
className="ml-4 p-2 text-sm font-medium text-red-600 hover:text-red-800 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50 rounded-md"
>
Delete
</button>
</li>
);
};
export default TodoItem;import React from 'react';
import { Todo } from '../types/todo';
import TodoItem from './TodoItem';
interface TodoListProps {
todos: Todo[];
onToggleComplete: (id: string) => void;
onDelete: (id: string) => void;
}
const TodoList: React.FC<TodoListProps> = ({ todos, onToggleComplete, onDelete }) => {
if (todos.length === 0) {
return (
<p className="text-center text-gray-500 mt-8">No todos yet! Add some tasks above.</p>
);
}
return (
<ul className="bg-white shadow-md rounded-lg overflow-hidden mt-6">
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggleComplete={onToggleComplete}
onDelete={onDelete}
/>
))}
</ul>
);
};
export default TodoList;import React, { useState, useEffect } from 'react';
import { Todo } from './types/todo';
import { getTodos, saveTodos } from './utils/localStorage';
import TodoList from './components/TodoList';
import { v4 as uuidv4 } from 'uuid'; // For unique IDs
const App: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [newTodoText, setNewTodoText] = useState<string>('');
// Load todos from local storage on initial render
useEffect(() => {
setTodos(getTodos());
}, []);
// Save todos to local storage whenever the todos state changes
useEffect(() => {
saveTodos(todos);
}, [todos]);
const handleAddTodo = (e: React.FormEvent) => {
e.preventDefault();
if (newTodoText.trim() === '') return;
const newTodo: Todo = {
id: uuidv4(), // Generate a unique ID
text: newTodoText.trim(),
completed: false,
};
setTodos((prevTodos) => [...prevTodos, newTodo]);
setNewTodoText('');
};
const handleToggleComplete = (id: string) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
const handleDeleteTodo = (id: string) => {
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
};
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-4">
<div className="bg-white p-8 rounded-lg shadow-xl w-full max-w-md">
<h1 className="text-4xl font-extrabold text-center text-gray-900 mb-8">
Todo List
</h1>
<form onSubmit={handleAddTodo} className="flex gap-2 mb-6">
<input
type="text"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="Add a new todo..."
className="flex-grow p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-lg"
/>
<button
type="submit"
className="px-5 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 text-lg font-semibold"
>
Add
</button>
</form>
<TodoList
todos={todos}
onToggleComplete={handleToggleComplete}
onDelete={handleDeleteTodo}
/>
</div>
</div>
);
};
export default App;import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css'; // Import Tailwind CSS
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);You'll need to install uuid for unique IDs: npm install uuid @types/uuid.
{
"name": "my-todo-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}-
Create the Project Directory: If you haven't already, create a new directory for your project and navigate into it.
mkdir my-todo-app cd my-todo-app -
Initialize React with TypeScript and Vite:
npm create vite@latest . -- --template react-ts(The
.means create in the current directory) -
Install Dependencies:
npm install
-
Install Tailwind CSS and its dependencies:
npm install -D tailwindcss postcss autoprefixer
-
Initialize Tailwind CSS Configuration: This command generates
tailwind.config.jsandpostcss.config.js.npx tailwindcss init -p
-
Install
uuidfor unique IDs:npm install uuid @types/uuid
-
Copy the Code: Replace the content of the files in your
my-todo-appdirectory with the code provided above, ensuring the correct file paths (src/types/todo.ts,src/utils/localStorage.ts, etc.). -
Start the Development Server:
npm run dev
Your browser should automatically open to
http://localhost:5173/(or a similar port), displaying the Todo List application.
Now you have a fully functional Todo List application with persistence using local storage, built with React, TypeScript, and Tailwind CSS!