TL;DR: Build a React todo app by scaffolding a project with Vite, then creating a single functional component that manages todos with the useState hook and persists them with useEffect. You’ll implement add, toggle, and delete actions through immutable state updates and render the list with a controlled input form.
Step 1: Scaffold the Project
Run npm create vite@latest todo-app -- --template react, then cd todo-app and npm install. Start the dev server with npm run dev. Tip: Vite gives you fast refresh, so you’ll see changes instantly as you build.
If you want to dig deeper, check out our guide on GLP-1 Drugs: How They’re Reshaping the Food & Beverage Indus.
Step 2: Set Up State with useState
In App.jsx, import useState and create two pieces of state: const [todos, setTodos] = useState([]) and const [input, setInput] = useState(""). The first holds your list; the second controls the text field. Tip: keep state as flat and minimal as possible—derive everything else.
Step 3: Build the Add Function
Write a handleAdd function that trims the input, guards against empty strings, and calls setTodos([...todos, { id: Date.now(), text: input, done: false }]). Clear the input afterward. Tip: use crypto.randomUUID() instead of Date.now() if you need guaranteed unique IDs.
Step 4: Implement Toggle and Delete
For toggling, map over todos and flip done where the ID matches: todos.map(t => t.id === id ? { ...t, done: !t.done } : t). For deletion, filter by ID: todos.filter(t => t.id !== id). Tip: never mutate the array or objects directly—always return new copies so React detects the change.
Step 5: Render the UI
Return a form with a controlled input (value={input}, onChange={e => setInput(e.target.value)}) and an onSubmit handler that calls handleAdd. Below it, map todos to list items with a checkbox, the text, and a delete button. Tip: add a strikethrough style when todo.done is true for clear visual feedback.
Step 6: Persist with useEffect
Import useEffect and add a side effect that saves todos to localStorage whenever they change. Initialize state lazily from storage: useState(() => JSON.parse(localStorage.getItem("todos")) ?? []). Tip: wrap the JSON parse in a try/catch to handle corrupted data gracefully.
FAQ
Q: Do I need useReducer instead of useState?
A: Not for a simple todo app—useState is sufficient. Switch to useReducer once your state logic grows complex or multiple actions share update patterns.
Q: Why isn’t my list updating after I change an item?
A: You’re likely mutating state directly. Always create new arrays and objects with spread syntax or map/filter so React sees a new reference.
Q: How do I stop the page from reloading on form submit?
A: Call e.preventDefault() at the top of your submit handler. Without it, the browser performs a full page reload and wipes your state.
Leave a Reply