Mercurial
diff react_games/src/TODO/main.tsx @ 37:fb9bcd3145cb
[ReactGames] Few games I made using react just to practice few things.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 01 Dec 2025 20:22:47 -0800 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/react_games/src/TODO/main.tsx Mon Dec 01 20:22:47 2025 -0800 @@ -0,0 +1,223 @@ +/* + * 📝 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 +}