Mercurial
view react_games/src/TODO/main.tsx @ 138:1f023b8bf9c3
[Test]
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 09 Jan 2026 11:35:07 -0800 |
| parents | fb9bcd3145cb |
| children |
line wrap: on
line source
/* * 📝 React Todo App Challenge (1 Hour) * ❓Problem * Build a simple Todo application using React. The app should allow users to: * * - Add a new todo item. * * - Mark a todo as completed. * * - Delete a todo. * * Filter todos by: * * - All * - Active * - Completed * * 💡Bonus (if time allows) * Persist todos in localStorage. * * Use React Context + useReducer to manage global state. * * Add a clear completed button. * * 📦 Requirements * Use functional components + hooks only. * * * * No styling is required, but if you have time, make it readable. * * Don’t use external state management libraries like Redux. * * Components * * TODOs * Inputs * buttons filtering * * TODO States * - Active * - Completed * - Deleted */ import React, { CSSProperties, Dispatch, InputHTMLAttributes, memo, useMemo, useRef, useState } from "react"; enum TodoState { ACTIVE="active", COMPLETED="completed", DELETED="deleted", } interface Todo { id: string; value: string; state: TodoState; } const INITIAL_TODOS: Todo[] = [ {id: "1", value: "I want to buy pizza", state: TodoState.ACTIVE}, {id: "2", value: "I want to buy pizza completed", state: TodoState.COMPLETED}, {id: "3", value: "I want to buy pizza deleted", state: TodoState.DELETED}, ] function changeStateTodo( todoId: string, todoState: TodoState, setTodoCallback: Dispatch<React.SetStateAction<Todo[]>>, ): void { setTodoCallback( (todos: Todo[]) => todos.map((todo: Todo) => { if (todo.id === todoId) { return { ...todo, state: todoState }; } return todo; }) ) } interface TodoComponentProp { todo: Todo; setTodos: Dispatch<React.SetStateAction<Todo[]>>; } const TodoComponent = memo(({todo, setTodos}: TodoComponentProp) => { const tdStyle: CSSProperties = { padding: "0.5rem", borderBottom: "1px solid #ddd", }; const rowStyle: CSSProperties = { backgroundColor: "#fff", }; return ( <tr key={todo.id} style={rowStyle}> <td style={tdStyle}>{todo.value}</td> <td style={tdStyle}> <span style={{ textTransform: "capitalize" }}>{todo.state}</span> </td> <td style={tdStyle}> {todo.state !== TodoState.COMPLETED && ( <button onClick={() => changeStateTodo(todo.id, TodoState.COMPLETED, setTodos)}> ✅ </button> )} {todo.state !== TodoState.DELETED && ( <button onClick={() => changeStateTodo(todo.id, TodoState.DELETED, setTodos)}> 🗑️ </button> )} {todo.state !== TodoState.ACTIVE && ( <button onClick={() => changeStateTodo(todo.id, TodoState.ACTIVE, setTodos)}> 🟢 </button> )} </td> </tr> ) }) const TODO = () => { const [todos, setTodos] = useState<Todo[]>(INITIAL_TODOS); const [todoFilter, setTodoFilters] = useState<TodoState | null>(null); const inputRef = useRef<HTMLInputElement>(null); const style: CSSProperties = { display: "flex", justifyContent: "center", alignItems: "center", height: "100vh", width: "100vw", } const filterTodos = useMemo(() => { return todoFilter ? todos.filter((t) => t.state === todoFilter) : todos; }, [todos, todoFilter]); const handleCreateTodo = (event: React.FormEvent) => { event.preventDefault(); const input = inputRef.current; if (!input || !input.value.trim()) return; const newValue = input.value.trim(); setTodos( (todos) => [ ...todos, { value: newValue, id: crypto.randomUUID(), state: TodoState.ACTIVE } ] ); input.value = ""; } const thStyle: CSSProperties = { textAlign: "left", padding: "0.5rem", backgroundColor: "#f0f0f0", borderBottom: "2px solid #ddd", }; const buttonStyle = (isActive: boolean): CSSProperties => { return { padding: "0.5rem 1rem", borderRadius: "6px", border: "1px solid #ccc", backgroundColor: isActive ? "#efefef" : "#fff", marginBottom: "1rem", } }; return ( <div style={style}> <div> <h1> TODOs: </h1> <h2> Filter by: {todoFilter != null ? todoFilter : "All"} </h2> <div style={{display: "flex", width: "100%", justifyContent: "space-between"}}> <button style={buttonStyle(todoFilter===TodoState.ACTIVE)} onClick={() => setTodoFilters(TodoState.ACTIVE)}> Active </button> <button style={buttonStyle(todoFilter===TodoState.DELETED)} onClick={() => setTodoFilters(TodoState.DELETED)}> Deleted </button> <button style={buttonStyle(todoFilter===TodoState.COMPLETED)} onClick={() => setTodoFilters(TodoState.COMPLETED)}> Completed </button> <button style={buttonStyle(todoFilter===null)} onClick={() => setTodoFilters(null)}> ALL </button> </div> <table style={{ borderCollapse: "collapse", width: "100%", marginBottom: "1rem" }}> <thead> <tr> <th style={thStyle}>Task</th> <th style={thStyle}>Status</th> <th style={thStyle}>Action</th> </tr> </thead> <tbody> { filterTodos.map((todo) => ( <TodoComponent key={todo.id} todo={todo} setTodos={setTodos}/> )) } </tbody> </table> <form onSubmit={handleCreateTodo}> <input ref={inputRef} placeholder={"Add a new todo..."} style={{ padding: "0.5rem", border: "1px solid #ccc", borderRadius: "4px", width: "300px", marginRight: "0.5rem", }} /> <button type="submit" style={{ padding: "0.5rem 1rem", borderRadius: "4px" }}>Add</button> </form> </div> </div> ); } export { TODO }