Skip to content
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

Implement todo update feature #798

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

OlhaMomot
Copy link

Copy link

@BudnikOleksii BudnikOleksii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work 👍
Left some suggestions, check them.
Also, show to user what todos are updating when user clicks the toggle all button.

src/App.tsx Outdated
Comment on lines 48 to 62
useEffect(() => {
let timer: NodeJS.Timeout | undefined;

if (errorMessage) {
timer = setTimeout(() => {
setErrorMessage('');
}, 3000);
}

return () => {
if (timer) {
clearTimeout(timer);
}
};
}, [errorMessage]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's create a component ErrorNotification and move this logic and UI to it

src/App.tsx Outdated
Comment on lines 64 to 74
const onToggleAll = async () => {
if (activeTodos.length) {
activeTodos.forEach(
currentTodo => updateTodoHandler(currentTodo, { completed: true }),
);
} else {
todos.forEach(
currentTodo => updateTodoHandler(currentTodo, { completed: false }),
);
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to create an array of requests and then handle them with Promise.all.
Also, show to user which todos are updating, add a loader to them

src/App.tsx Outdated
Comment on lines 81 to 96
<header className="todoapp__header">
{!!todos.length
&& (
<button
type="button"
className={classNames(
'todoapp__toggle-all',
{ active: !activeTodos.length },
)}
data-cy="ToggleAllButton"
onClick={onToggleAll}
/>
)}

<Form setTempTodo={setTempTodo} />
</header>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a component Header

src/App.tsx Outdated
Comment on lines 112 to 113
<div
data-cy="ErrorNotification"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned before, create a component ErrorNotification

type Props = {
activeTodos: Todo[],
filter: string,
FilterOption: { [key: string]: string },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have enum FilterOption, use it instead

Comment on lines 15 to 17
export const TodoItem: React.FC<Props> = (
{ todo }: Props,
) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const TodoItem: React.FC<Props> = (
{ todo }: Props,
) => {
export const TodoItem: React.FC<Props> = ({ todo }) => {

You have already set Props type in the generic

Comment on lines 50 to 53
} else if (todoTitle === todo.title) {
setIsEditing(false);

return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If todoTitle is the same we have an infinite spinner as you set it previously setIsItemLoading(true); and just return in this case

event.preventDefault();
setIsItemLoading(true);

if (todoTitle !== todo.title && todoTitle !== '') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You compare todoTitle !== todo.title it two times, create a variable.
Also, you don't need to compare with an empty string, just check if todoTitle exists.
https://developer.mozilla.org/en-US/docs/Glossary/Truthy

Comment on lines 21 to 22
{tempTodo
&& <TodoItem todo={tempTodo} />}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{tempTodo
&& <TodoItem todo={tempTodo} />}
{tempTodo && (
<TodoItem todo={tempTodo} />
)}

Comment on lines 39 to 40
const [isLoadingMap, setIsLoadingMap]
= useState<{ [key: number]: boolean } | {}>({});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just store an array of loading todos ids and it will be a bit easy to manage

Copy link

@Moroz-Dmytro Moroz-Dmytro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. When I rename todo to empty(enter just spaces) -> todo should be deleted
image
  1. "1 items left", but all todos are checked
image

src/App.tsx Outdated
Comment on lines 25 to 35
return todos.filter(({ completed }) => {
switch (filter) {
case FilterOption.Active:
return !completed;
case FilterOption.Completed:
return completed;
case FilterOption.All:
default:
return true;
}
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this logic to separate helper function

src/App.tsx Outdated
Comment on lines 53 to 54
{!!filteredTodos.length
&& <TodoList todos={filteredTodos} tempTodo={tempTodo} />}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{!!filteredTodos.length
&& <TodoList todos={filteredTodos} tempTodo={tempTodo} />}
{!!filteredTodos.length && (
<TodoList todos={filteredTodos} tempTodo={tempTodo} />
)}

src/App.tsx Outdated
Comment on lines 56 to 63
{!!todos.length
&& (
<Footer
activeTodos={activeTodos}
filter={filter}
setFilter={setFilter}
/>
)}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{!!todos.length
&& (
<Footer
activeTodos={activeTodos}
filter={filter}
setFilter={setFilter}
/>
)}
{!!todos.length && (
<Footer
activeTodos={activeTodos}
filter={filter}
setFilter={setFilter}
/>
)}

src/App.tsx Outdated
Comment on lines 64 to 65
</div>
<ErrorNotification

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
</div>
<ErrorNotification
</div>
<ErrorNotification

Comment on lines 21 to 23
const completedTodos = useMemo(() => {
return todos.filter(({ completed }) => completed === true);
}, [todos]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const completedTodos = useMemo(() => {
return todos.filter(({ completed }) => completed === true);
}, [todos]);
const completedTodos = todos.filter(({ completed }) => completed);

useMemo is useless here

.map(({ id }) => deleteTodoHandler(id)));
};

const isClearButtonInvisible = completedTodos.length === 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isClearButtonInvisible = completedTodos.length === 0;
const isClearButtonInvisible = !completedTodos.length;

But you don't really need variable for this

Comment on lines 76 to 88
if (activeTodos.length) {
Promise.all(activeTodos
.map(currentTodo => updateTodoHandler(
currentTodo,
{ completed: true },
)));
} else {
Promise.all(todos
.map(currentTodo => updateTodoHandler(
currentTodo,
{ completed: false },
)));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY. a lot of code repeats, try to refactor this

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add await before Promise.all

const [errorMessage, setErrorMessage] = useState('');
const [isLoadingTodoIds, setIsLoadingTodoIds] = useState<number[]>([]);

const addTodoHandler = async (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const addTodoHandler = async (
const handleAddTodo = async (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same all other names


setTodos((currentTodos: Todo[]) => {
return currentTodos.map((currentTodo) => {
return currentTodo.id === updatedTodo.id ? updatedTodo : currentTodo;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return currentTodo.id === updatedTodo.id ? updatedTodo : currentTodo;
return currentTodo.id === updatedTodo.id
? updatedTodo
: currentTodo;

Comment on lines 137 to 138
'is-active': (isLoadingTodoIds.includes(todo.id))
|| isItemLoading,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to vatiable

Copy link

@dffUqp dffUqp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM:fire: but needs a few minor fixes:smiley:

Comment on lines 75 to 79
document.addEventListener('keyup', (e) => {
if (e.key === 'Escape') {
setIsEditing(false);
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange situations with this code, It works even if I don't have any todos on the page

I think this article can help you to solve this problem
https://www.freecodecamp.org/news/react-useeffect-absolute-beginners/#:~:text=What%20are%20side%20effects%20in,give%20us%20a%20predictable%20result.

2023-09-29.17-48-55.mov

handleUpdateTodo,
} = useTodo();

const onCheckboxChange = async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is just a recomendation:

Component events start with the prefix on but the function that handles these events should be named with the handle prefix

@@ -0,0 +1,114 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's fix this eslint issue:smiley:

try to work around aria-label , I think it can help

Comment on lines 28 to 32
useEffect(() => {
if (todoTitleField.current) {
todoTitleField.current.focus();
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect without a dependency array, is a bad practice

Currently, this useEffect triggers on every Header render, try to look at how it works on the mate demo.

I think there they focus input only after delete or add todo (after changing the amount of todos)

? (
<form
onSubmit={onTodoSave}
onBlur={onTodoSave}
Copy link

@dffUqp dffUqp Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that a lot of tests is not valid, but in your case you have some valid tests failed

For example:

  15) 
       Renaming
         Edit Form
           on enter before recieved a response
             should stay while waiting:
 on click
         5) should send requests only for not completed todos

Comment on lines 12 to 19
{todos.map(todo => {
return (
<TodoItem
todo={todo}
key={todo.id}
/>
);
})}
Copy link

@dffUqp dffUqp Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove return keyword here

Comment on lines 5 to 23
const {
todos,
handleAddTodo,
handleDeleteTodo,
handleUpdateTodo,
errorMessage,
setErrorMessage,
isLoadingTodoIds,
} = useContext(TodoContext);

return {
todos,
handleAddTodo,
handleDeleteTodo,
handleUpdateTodo,
errorMessage,
setErrorMessage,
isLoadingTodoIds,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it work?

Suggested change
const {
todos,
handleAddTodo,
handleDeleteTodo,
handleUpdateTodo,
errorMessage,
setErrorMessage,
isLoadingTodoIds,
} = useContext(TodoContext);
return {
todos,
handleAddTodo,
handleDeleteTodo,
handleUpdateTodo,
errorMessage,
setErrorMessage,
isLoadingTodoIds,
};
const todoContextValues = useContext(TodoContext);
return todoContextValues

Copy link

@roman-mirzoian roman-mirzoian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done 👍

data-cy="ErrorNotification"
className={
classNames(
'notification is-danger is-light has-text-weight-normal',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'notification is-danger is-light has-text-weight-normal',
'notification', 'is-danger', 'is-light', 'has-text-weight-normal',

<div
data-cy="TodoLoader"
className={classNames(
'modal overlay',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'modal overlay',
'modal', 'overlay',

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants