-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop #1060
base: master
Are you sure you want to change the base?
Develop #1060
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey. Good job. There are some things that has to be fixed. Also i have left some comments to improve the code.
- Todo App content div is smaller than its supposed to be. Look how input is overflowing.
- The 'Toggle All' button should function by toggling all active to-dos to the completed state. When all to-dos are already completed, it should toggle them back to the active state.
It looks like some tests didn't pass. Did the tests pass locally?
src/components/Footer/Footer.tsx
Outdated
const incompleteTodos = todos.filter(todo => todo.completed === false).length; | ||
const isEveryIncompleted = todos.every(todo => !todo.completed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think about caching results of these calculations.
src/components/Footer/Footer.tsx
Outdated
export const Footer = () => { | ||
const { todos, filter, setFilter, deleteMultipleTodos } = useTodoContext(); | ||
|
||
const incompleteTodos = todos.filter(todo => todo.completed === false).length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable name suggests that it holds all the incomplete to-dos themselves, not just the count of them.
src/components/Footer/Footer.tsx
Outdated
<a | ||
href="#/" | ||
className={`filter__link ${filter === 'All' ? 'selected' : ''}`} | ||
data-cy="FilterLinkAll" | ||
onClick={() => setFilter('All')} | ||
> | ||
All | ||
</a> | ||
|
||
<a | ||
href="#/active" | ||
className={`filter__link ${filter === 'Active' ? 'selected' : ''}`} | ||
data-cy="FilterLinkActive" | ||
onClick={() => setFilter('Active')} | ||
> | ||
Active | ||
</a> | ||
|
||
<a | ||
href="#/completed" | ||
className={`filter__link ${filter === 'Completed' ? 'selected' : ''}`} | ||
data-cy="FilterLinkCompleted" | ||
onClick={() => setFilter('Completed')} | ||
> | ||
Completed | ||
</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is duplication in setting filters for different views (All, Active, Completed). This can be refactored to reduce redundancy.
src/components/Todos/Todos.tsx
Outdated
if (editingTodoId !== null) { | ||
if (editingTitle.trim() === '') { | ||
deleteTodo(editingTodoId); | ||
} else { | ||
updateTodoTitle(editingTodoId, editingTitle); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The form submission logic could be refactored to avoid deeply nested if conditions.
if (editingTodoId !== null) { | |
if (editingTitle.trim() === '') { | |
deleteTodo(editingTodoId); | |
} else { | |
updateTodoTitle(editingTodoId, editingTitle); | |
} | |
} | |
if (editingTodoId === null) { | |
return | |
} | |
if (editingTitle.trim() === '') { | |
deleteTodo(editingTodoId); | |
} else { | |
updateTodoTitle(editingTodoId, editingTitle); | |
} |
src/components/Todos/Todos.tsx
Outdated
if (headerInputRef.current) { | ||
headerInputRef.current.focus(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create a utility function that enables triggering focus on headerInputRef.
return ( | ||
<section className="todoapp__main" data-cy="TodoList"> | ||
{/* This is a completed todo */} | ||
{filteredTodos.map(todo => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember about destructure syntax.
src/components/Todos/Todos.tsx
Outdated
useEffect(() => { | ||
if (editingTodoId !== null && renameInputRef.current) { | ||
renameInputRef.current.focus(); | ||
} | ||
|
||
const handleClickOutsideForm = (event: MouseEvent) => { | ||
if ( | ||
editingTodoId !== null && | ||
renameInputRef.current && | ||
!renameInputRef.current.contains(event.target as Node) | ||
) { | ||
if (editingTitle.trim() === '') { | ||
deleteTodo(editingTodoId); | ||
} else { | ||
updateTodoTitle(editingTodoId, editingTitle); | ||
} | ||
|
||
setEditingTodoId(null); | ||
|
||
setTimeout(() => { | ||
if (headerInputRef.current) { | ||
headerInputRef.current.focus(); | ||
} | ||
}, 0); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this logic. You can just simply listen for onBlur event on the input
src/components/Todos/Todos.tsx
Outdated
const handlePressEscape = (event: KeyboardEvent) => { | ||
if (event.key === 'Escape' && editingTodoId !== null) { | ||
setEditingTodoId(null); | ||
} | ||
}; | ||
|
||
if (editingTodoId !== null) { | ||
document.addEventListener('mousedown', handleClickOutsideForm); | ||
document.addEventListener('keyup', handlePressEscape); | ||
} | ||
|
||
return () => { | ||
document.removeEventListener('mousedown', handleClickOutsideForm); | ||
document.removeEventListener('keyup', handlePressEscape); | ||
}; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the onKeyUp event to the input. You don't need to listen for the event if it's not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest creating a TodoItem component. This way, you can colocate the logic and state within that component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think about optimizing it a bit. Use useMemo and useCallback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job! There are still some things that you should change.
<a | ||
key={key} | ||
href={href} | ||
className={`filter__link ${filter === currentFilter ? 'selected' : ''}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember that you have installed lib to handle this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What lib?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
classnames
src/components/Todos/Todos.tsx
Outdated
useEffect(() => { | ||
if (editingTodoId !== null && renameInputRef.current) { | ||
renameInputRef.current.focus(); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unnecessary to run on every change. Use a dependency array.
src/components/Todos/Todos.tsx
Outdated
const [editingTodoId, setEditingTodoId] = useState<number | null>(null); | ||
const [editingTitle, setEditingTitle] = useState(''); | ||
const renameInputRef = useRef<HTMLInputElement | null>(null); | ||
const checkboxRef = useRef<HTMLInputElement | null>(null); | ||
const { toggleTodoStatus, updateTodoTitle, deleteTodo, filteredTodos } = | ||
useTodoContext(); | ||
|
||
const handleSubmit = useCallback( | ||
(event: React.FormEvent) => { | ||
event.preventDefault(); | ||
|
||
if (editingTodoId === null) { | ||
return; | ||
} | ||
|
||
if (editingTitle.trim() === '') { | ||
deleteTodo(editingTodoId); | ||
} else { | ||
updateTodoTitle(editingTodoId, editingTitle); | ||
} | ||
|
||
setEditingTodoId(null); | ||
}, | ||
[deleteTodo, editingTitle, editingTodoId, updateTodoTitle], | ||
); | ||
|
||
const handleDoubleClick = useCallback((id: number, currentTitle: string) => { | ||
setEditingTodoId(id); | ||
setEditingTitle(currentTitle); | ||
}, []); | ||
|
||
const handleOnBlur = useCallback( | ||
(event: React.FormEvent) => { | ||
handleSubmit(event); | ||
}, | ||
[handleSubmit], | ||
); | ||
|
||
const handleKeyUp = useCallback( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've created the TodoItem component, but the logic for editing hasn't been moved there yet. You can move the rename input state and also the editingTodoId. This way, you don't need to track which todo is being edited by its ID; you can simply use a state like isEdited
const value = { | ||
todos, | ||
addTodo, | ||
toggleTodoStatus, | ||
toggleMultipleTodosStatus, | ||
updateTodoTitle, | ||
deleteTodo, | ||
headerInputRef, | ||
filter, | ||
setFilter, | ||
filteredTodos, | ||
deleteMultipleTodos, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one also need to be wrapped in memo. This object is recreated on every rerender.
const value = { | ||
todos, | ||
addTodo, | ||
toggleTodoStatus, | ||
toggleMultipleTodosStatus, | ||
updateTodoTitle, | ||
deleteTodo, | ||
headerInputRef, | ||
filter, | ||
setFilter, | ||
filteredTodos, | ||
deleteMultipleTodos, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you can do is also separate state and state setters. https://devtrium.com/posts/how-use-react-context-pro#separate-state-and-state-setters-if-necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update your demo as your last deployment was 4 days ago and doesn't include all the fixes you've made
localy all tests passed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job!
DEMO LINK