add task solution - #1332
Conversation
nkoropka
commented
Jul 29, 2026
- DEMO LINK
Zoolgrand
left a comment
There was a problem hiding this comment.
Great job overall! I've found a couple of improvements you can add to your code — we need to refactor those two components and remove the code comments across the app.
| const [isEditing, setIsEditing] = useState(false); | ||
| const [editingTitle, setEditingTitle] = useState(todo.title); | ||
|
|
||
| const isCancelledRef = useRef(false); | ||
|
|
||
| const handleSave = (event?: React.FormEvent) => { | ||
| if (event) { | ||
| event.preventDefault(); | ||
| } | ||
|
|
||
| if (isCancelledRef.current || !isEditing) { | ||
| return; | ||
| } | ||
|
|
||
| setIsEditing(false); | ||
|
|
||
| const trimmedTitle = editingTitle.trim(); | ||
|
|
||
| if (!trimmedTitle) { | ||
| onDelete(todo.id); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| if (trimmedTitle !== todo.title) { | ||
| dispatch({ | ||
| type: 'RENAME', | ||
| payload: { id: todo.id, title: trimmedTitle }, | ||
| }); | ||
| } | ||
|
|
||
| onFocusNewTodo?.(); | ||
| }; | ||
|
|
||
| const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { | ||
| if (event.key === 'Escape') { | ||
| isCancelledRef.current = true; | ||
| setEditingTitle(todo.title); | ||
|
|
||
| setIsEditing(false); | ||
| onFocusNewTodo?.(); | ||
| } | ||
| }; | ||
|
|
||
| const handleBlur = () => { | ||
| if (!isEditing) { | ||
| return; | ||
| } | ||
|
|
||
| if (isEditing && editingTitle !== todo.title) { | ||
| handleSave(); | ||
| } else { | ||
| setIsEditing(false); | ||
| } | ||
| }; | ||
|
|
||
| const handleDoubleClick = () => { | ||
| setIsEditing(true); | ||
| setEditingTitle(todo.title); | ||
| isCancelledRef.current = false; | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| setEditingTitle(todo.title); | ||
| }, [todo.title]); |
There was a problem hiding this comment.
Could you extract this logic into custom hook useTodoItem?
| value={editingTitle} | ||
| onChange={e => setEditingTitle(e.target.value)} | ||
| onBlur={handleBlur} | ||
| onKeyDown={handleKeyDown} |
There was a problem hiding this comment.
Please use onKeyUp to match the specification
| useEffect(() => { | ||
| newTodoInputRef.current?.focus(); | ||
| }, []); |
There was a problem hiding this comment.
You already have autofocus on your input
| const [title, setTitle] = useState(''); | ||
| const [filter, setFilter] = useState<FilteredStatus>('all'); | ||
|
|
||
| const newTodoInputRef = useRef<HTMLInputElement>(null); | ||
|
|
||
| useEffect(() => { | ||
| newTodoInputRef.current?.focus(); | ||
| }, []); | ||
|
|
||
| const handleSubmit = (event: React.FormEvent) => { | ||
| event.preventDefault(); | ||
|
|
||
| const trimmedTitle = title.trim(); | ||
|
|
||
| if (!trimmedTitle) { | ||
| return; | ||
| } | ||
|
|
||
| dispatch({ type: 'ADD', payload: trimmedTitle }); | ||
|
|
||
| setTitle(''); | ||
| }; | ||
|
|
||
| const handleDeleteTodo = (todoId: number) => { | ||
| dispatch({ type: 'DELETE', payload: todoId }); | ||
| newTodoInputRef.current?.focus(); | ||
| }; | ||
|
|
||
| const hasCompletedTodos = todos.some(t => t.completed); | ||
|
|
||
| const activeTodosCount = todos.filter(t => !t.completed).length; | ||
|
|
||
| const focusNewTodoInput = () => { | ||
| newTodoInputRef.current?.focus(); | ||
| }; |
There was a problem hiding this comment.
Extract this logic into custom hook UseTodo
| onClick={() => { | ||
| const areAllCompleted = todos.every(t => t.completed); | ||
|
|
||
| dispatch({ | ||
| type: 'TOGGLE_ALL', | ||
| payload: !areAllCompleted, | ||
| }); | ||
| }} |
| onClick={() => { | ||
| dispatch({ type: 'CLEAR' }); | ||
| newTodoInputRef.current?.focus(); | ||
| }} |
There was a problem hiding this comment.
Here is smaller example but we can still extract this into function
| ))} | ||
| </section> | ||
|
|
||
| {/* Hide the footer if there are no todos */} |
| onFocusNewTodo?.(); | ||
| }; | ||
|
|
||
| const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { |
There was a problem hiding this comment.
rename this to handle keyUp event as requires specification
2pasha
left a comment
There was a problem hiding this comment.
good job! 👏
take a look on comments below ⬇️
| onChange={e => setEditingTitle(e.target.value)} | ||
| onBlur={handleBlur} | ||
| onKeyDown={handleKeyDown} | ||
| onKeyDown={handleKeyUp} |
| // const handleBlur = () => { | ||
| // if (!isEditing) { | ||
| // return; | ||
| // } | ||
|
|
||
| // if (isEditing && editingTitle !== todo.title) { | ||
| // handleSave(); | ||
| // } else { | ||
| // setIsEditing(false); | ||
| // } | ||
| // }; |
| <a | ||
| href="#/" | ||
| className={classNames('filter__link', { | ||
| selected: filter === 'all', | ||
| })} | ||
| data-cy="FilterLinkAll" | ||
| onClick={() => setFilter('all')} | ||
| > | ||
| All | ||
| </a> | ||
|
|
||
| <a | ||
| href="#/active" | ||
| className={classNames('filter__link', { | ||
| selected: filter === 'active', | ||
| })} | ||
| data-cy="FilterLinkActive" | ||
| onClick={() => setFilter('active')} | ||
| > | ||
| Active | ||
| </a> | ||
|
|
||
| <a | ||
| href="#/completed" | ||
| className={classNames('filter__link', { | ||
| selected: filter === 'completed', | ||
| })} | ||
| data-cy="FilterLinkCompleted" | ||
| onClick={() => setFilter('completed')} | ||
| > | ||
| Completed | ||
| </a> |
There was a problem hiding this comment.
you may add all your filter values to kind of array and map through it to avoid code duplication
Zoolgrand
left a comment
There was a problem hiding this comment.
Good job.
Approved
There is still some comments in your code (search for {/ in code) could you remove this as well?