comparison 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
comparison
equal deleted inserted replaced
36:84672efec192 37:fb9bcd3145cb
1 /*
2 * 📝 React Todo App Challenge (1 Hour)
3 * ❓Problem
4 * Build a simple Todo application using React. The app should allow users to:
5 *
6 * - Add a new todo item.
7 *
8 * - Mark a todo as completed.
9 *
10 * - Delete a todo.
11 *
12 * Filter todos by:
13 *
14 * - All
15 * - Active
16 * - Completed
17 *
18 * 💡Bonus (if time allows)
19 * Persist todos in localStorage.
20 *
21 * Use React Context + useReducer to manage global state.
22 *
23 * Add a clear completed button.
24 *
25 * 📦 Requirements
26 * Use functional components + hooks only.
27 *
28 *
29 *
30 * No styling is required, but if you have time, make it readable.
31 *
32 * Don’t use external state management libraries like Redux.
33 *
34 * Components
35 *
36 * TODOs
37 * Inputs
38 * buttons filtering
39 *
40 * TODO States
41 * - Active
42 * - Completed
43 * - Deleted
44 */
45 import React, { CSSProperties, Dispatch, InputHTMLAttributes, memo, useMemo, useRef, useState } from "react";
46
47 enum TodoState {
48 ACTIVE="active",
49 COMPLETED="completed",
50 DELETED="deleted",
51 }
52
53 interface Todo {
54 id: string;
55 value: string;
56 state: TodoState;
57 }
58
59 const INITIAL_TODOS: Todo[] = [
60 {id: "1", value: "I want to buy pizza", state: TodoState.ACTIVE},
61 {id: "2", value: "I want to buy pizza completed", state: TodoState.COMPLETED},
62 {id: "3", value: "I want to buy pizza deleted", state: TodoState.DELETED},
63 ]
64
65 function changeStateTodo(
66 todoId: string,
67 todoState: TodoState,
68 setTodoCallback: Dispatch<React.SetStateAction<Todo[]>>,
69 ): void {
70 setTodoCallback(
71 (todos: Todo[]) => todos.map((todo: Todo) => {
72 if (todo.id === todoId) {
73 return {
74 ...todo,
75 state: todoState
76 };
77 }
78 return todo;
79 })
80 )
81 }
82
83 interface TodoComponentProp {
84 todo: Todo;
85 setTodos: Dispatch<React.SetStateAction<Todo[]>>;
86 }
87
88 const TodoComponent = memo(({todo, setTodos}: TodoComponentProp) => {
89 const tdStyle: CSSProperties = {
90 padding: "0.5rem",
91 borderBottom: "1px solid #ddd",
92 };
93
94 const rowStyle: CSSProperties = {
95 backgroundColor: "#fff",
96 };
97
98 return (
99 <tr key={todo.id} style={rowStyle}>
100 <td style={tdStyle}>{todo.value}</td>
101 <td style={tdStyle}>
102 <span style={{ textTransform: "capitalize" }}>{todo.state}</span>
103 </td>
104 <td style={tdStyle}>
105 {todo.state !== TodoState.COMPLETED && (
106 <button onClick={() => changeStateTodo(todo.id, TodoState.COMPLETED, setTodos)}>
107
108 </button>
109 )}
110 {todo.state !== TodoState.DELETED && (
111 <button onClick={() => changeStateTodo(todo.id, TodoState.DELETED, setTodos)}>
112 🗑️
113 </button>
114 )}
115 {todo.state !== TodoState.ACTIVE && (
116 <button onClick={() => changeStateTodo(todo.id, TodoState.ACTIVE, setTodos)}>
117 🟢
118 </button>
119 )}
120 </td>
121 </tr>
122 )
123 })
124
125 const TODO = () => {
126 const [todos, setTodos] = useState<Todo[]>(INITIAL_TODOS);
127 const [todoFilter, setTodoFilters] = useState<TodoState | null>(null);
128 const inputRef = useRef<HTMLInputElement>(null);
129
130 const style: CSSProperties = {
131 display: "flex",
132 justifyContent: "center",
133 alignItems: "center",
134 height: "100vh",
135 width: "100vw",
136 }
137
138 const filterTodos = useMemo(() => {
139 return todoFilter ? todos.filter((t) => t.state === todoFilter) : todos;
140 }, [todos, todoFilter]);
141
142 const handleCreateTodo = (event: React.FormEvent) => {
143 event.preventDefault();
144 const input = inputRef.current;
145 if (!input || !input.value.trim()) return;
146
147 const newValue = input.value.trim();
148 setTodos(
149 (todos) => [
150 ...todos,
151 {
152 value: newValue,
153 id: crypto.randomUUID(),
154 state: TodoState.ACTIVE
155 }
156 ]
157 );
158 input.value = "";
159 }
160
161 const thStyle: CSSProperties = {
162 textAlign: "left",
163 padding: "0.5rem",
164 backgroundColor: "#f0f0f0",
165 borderBottom: "2px solid #ddd",
166 };
167
168 const buttonStyle = (isActive: boolean): CSSProperties => {
169 return {
170 padding: "0.5rem 1rem",
171 borderRadius: "6px",
172 border: "1px solid #ccc",
173 backgroundColor: isActive ? "#efefef" : "#fff",
174 marginBottom: "1rem",
175 }
176 };
177
178 return (
179 <div style={style}>
180 <div>
181 <h1> TODOs: </h1>
182 <h2> Filter by: {todoFilter != null ? todoFilter : "All"} </h2>
183 <div style={{display: "flex", width: "100%", justifyContent: "space-between"}}>
184 <button style={buttonStyle(todoFilter===TodoState.ACTIVE)} onClick={() => setTodoFilters(TodoState.ACTIVE)}> Active </button>
185 <button style={buttonStyle(todoFilter===TodoState.DELETED)} onClick={() => setTodoFilters(TodoState.DELETED)}> Deleted </button>
186 <button style={buttonStyle(todoFilter===TodoState.COMPLETED)} onClick={() => setTodoFilters(TodoState.COMPLETED)}> Completed </button>
187 <button style={buttonStyle(todoFilter===null)} onClick={() => setTodoFilters(null)}> ALL </button>
188 </div>
189 <table style={{ borderCollapse: "collapse", width: "100%", marginBottom: "1rem" }}>
190 <thead>
191 <tr>
192 <th style={thStyle}>Task</th>
193 <th style={thStyle}>Status</th>
194 <th style={thStyle}>Action</th>
195 </tr>
196 </thead>
197 <tbody>
198 {
199 filterTodos.map((todo) => (
200 <TodoComponent key={todo.id} todo={todo} setTodos={setTodos}/>
201 ))
202 }
203 </tbody>
204 </table>
205 <form onSubmit={handleCreateTodo}>
206 <input ref={inputRef} placeholder={"Add a new todo..."} style={{
207 padding: "0.5rem",
208 border: "1px solid #ccc",
209 borderRadius: "4px",
210 width: "300px",
211 marginRight: "0.5rem",
212 }}
213 />
214 <button type="submit" style={{ padding: "0.5rem 1rem", borderRadius: "4px" }}>Add</button>
215 </form>
216 </div>
217 </div>
218 );
219 }
220
221 export {
222 TODO
223 }