Q&A
How to make a Chakra UI and React to-do list app using hooks only
Overview
This combines two ideas: hooks-only React (no class components - all state via useState) and Chakra UI (an accessible, themeable component library so you write almost no raw CSS). The to-do list is a canonical CRUD-in-state exercise: a list of task objects you add to, toggle complete, and remove - always treating state immutably.
graph TD
A["useState: tasks[], input"] --> B[Add: setTasks([...tasks, new])]
A --> C[Toggle: map → flip done flag]
A --> D[Delete: filter out by id]
B & C & D --> E[Chakra components re-render list]
style A fill:#1e1b4b,color:#fff
Setup
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
Wrap your app in Chakra's provider:
import { ChakraProvider } from "@chakra-ui/react";
export default function Root() {
return <ChakraProvider><TodoApp /></ChakraProvider>;
}
The Component (hooks only)
import { useState } from "react";
import {
Box, Heading, Input, Button, Stack, HStack,
Checkbox, IconButton, Text
} from "@chakra-ui/react";
import { DeleteIcon } from "@chakra-ui/icons";
export default function TodoApp() {
const [tasks, setTasks] = useState([]); // [{ id, text, done }]
const [input, setInput] = useState("");
const addTask = () => {
const text = input.trim();
if (!text) return;
setTasks(prev => [...prev, { id: Date.now(), text, done: false }]);
setInput(""); // clear the field
};
const toggleTask = (id) =>
setTasks(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
const deleteTask = (id) =>
setTasks(prev => prev.filter(t => t.id !== id));
return (
<Box maxW="480px" mx="auto" mt={10} p={6} borderWidth="1px" borderRadius="lg">
<Heading size="md" mb={4}>My Tasks</Heading>
<HStack mb={4}>
<Input
value={input}
placeholder="Add a task…"
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTask()}
/>
<Button colorScheme="purple" onClick={addTask}>Add</Button>
</HStack>
<Stack spacing={2}>
{tasks.length === 0 && <Text color="gray.500">No tasks yet.</Text>}
{tasks.map(task => (
<HStack key={task.id} justify="space-between">
<Checkbox isChecked={task.done} onChange={() => toggleTask(task.id)}>
<Text as={task.done ? "s" : undefined}>{task.text}</Text>
</Checkbox>
<IconButton
aria-label="Delete task"
icon={<DeleteIcon />}
size="sm"
onClick={() => deleteTask(task.id)}
/>
</HStack>
))}
</Stack>
</Box>
);
}
What the Hooks Are Doing
tasksholds the list of task objects;inputholds the controlled-input value.- Add uses the functional updater
setTasks(prev => [...prev, newTask])- a new array (immutability), so React detects the change. - Toggle maps over tasks, returning a new object only for the matched id (spread to copy the rest).
- Delete filters the task out by id.
- Each task needs a stable
key(task.id) for efficient list reconciliation.
What Chakra Provides
Box, Stack, HStack handle layout/spacing via props (mb, spacing, justify); Input, Button, Checkbox, IconButton are accessible, themed components. You set colors with colorScheme and never touch a CSS file. Chakra components are keyboard- and screen-reader-friendly out of the box.
Optional Enhancements
- Persistence: sync
tasksto a backend or storage in auseEffect(note: browser storage isn't available in some sandboxed canvases). - Filtering: add a
filterstate (all/active/done) and derive the visible list. - Edit in place: track an
editingIdand swap theTextfor anInput.
Why This Matters in Interviews
This exercises hooks-only state management, controlled inputs, immutable updates (spread/map/filter), list rendering with stable keys, and integrating a component library. The discriminators: using functional state updates, never mutating the array in place, and giving each list item a stable key. It also shows you can build accessible UI quickly with a design system.
Common Pitfalls
- Mutating
tasksdirectly (tasks.push(...)) instead of creating a new array → no re-render. - Using the array index as
key- breaks when items are added/removed/reordered; use a stable id. - Forgetting to clear the input after adding.
- Not wrapping the app in
ChakraProvider(components render unstyled or error).
References
- Chakra UI docs: https://chakra-ui.com/docs/components
- React
useState: https://react.dev/reference/react/useState