Skip to content

Commit

Permalink
fixed esc bug
Browse files Browse the repository at this point in the history
  • Loading branch information
WatarJoy committed Oct 30, 2024
1 parent 38fa680 commit 6bfe432
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 66 deletions.
87 changes: 42 additions & 45 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import {
getTodos,
addTodo,
Expand All @@ -14,21 +14,33 @@ import { ErrorNotification } from './components/ErrorNotification';
import { FilterStates, ErrorMessages } from './types/enums';
import { TodoItem } from './components/TodoItem';

export const App: React.FC = () => {
export const App: FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [error, setError] = useState<string | null>(null);
const [error, setError] = useState<ErrorMessages>(ErrorMessages.DEFAULT);
const [filter, setFilter] = useState(FilterStates.ALL);
const [tempTodo, setTempTodo] = useState<Todo | null>(null);
const [title, setTitle] = useState('');
const [isAdding, setIsAdding] = useState(false);
const [isEditingId, setIsEditingId] = useState<number | null>(null);

const trimmedTitle = title.trim();

const fetchTodos = async () => {
try {
const data = await getTodos();

setTodos(data);
} catch (err) {
setError(ErrorMessages.LOAD_TODOS);
const timer = setTimeout(() => setError(ErrorMessages.DEFAULT), 3000);

return () => clearTimeout(timer);
}
};

const handleAddTodo = useCallback(async (): Promise<void> => {
if (title.trim() === '') {
if (trimmedTitle === '') {
setError(ErrorMessages.NAMING_TODOS);
setTimeout(() => setError(null), 3000);

return;
}

setIsAdding(true);
Expand All @@ -43,15 +55,14 @@ export const App: React.FC = () => {

setTempTodo(newTempTodo);

const newTodo = await addTodo(title.trim());
const newTodo = await addTodo(trimmedTitle);

setTodos(prevTodos => [...prevTodos, newTodo]);
setTempTodo(null);
setTitle('');
} catch {
setError(ErrorMessages.ADDING_TODOS);
setTempTodo(null);
setTimeout(() => setError(null), 3000);
} finally {
setIsAdding(false);
setTimeout(() => {
Expand All @@ -70,7 +81,6 @@ export const App: React.FC = () => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== todoId));
} catch {
setError(ErrorMessages.DELETING_TODOS);
setTimeout(() => setError(null), 3000);
} finally {
const input =
document.querySelector<HTMLInputElement>('.todoapp__new-todo');
Expand All @@ -92,7 +102,6 @@ export const App: React.FC = () => {
);
} catch {
setError(ErrorMessages.UPDATE_TODOS);
setTimeout(() => setError(null), 3000);
}
},
[],
Expand All @@ -101,25 +110,25 @@ export const App: React.FC = () => {
const handleUpdateTodoTitle = useCallback(
async (todoId: number, newTitle: string): Promise<void> => {
const existingTodo = todos.find(todo => todo.id === todoId);
const newTitleTrimmed = newTitle.trim();

if (!existingTodo) {
return;
}

if (newTitle.trim() === existingTodo.title) {
if (newTitleTrimmed === existingTodo.title) {
setIsEditingId(null);

return;
}

if (newTitle.trim() === '') {
if (newTitleTrimmed === '') {
try {
await deleteTodo(todoId);
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== todoId));
setIsEditingId(null);
} catch {
setError(ErrorMessages.DELETING_TODOS);
setTimeout(() => setError(null), 3000);
}

return;
Expand All @@ -135,7 +144,6 @@ export const App: React.FC = () => {
setIsEditingId(null);
} catch {
setError(ErrorMessages.UPDATE_TODOS);
setTimeout(() => setError(null), 3000);
}
},
[todos],
Expand Down Expand Up @@ -180,7 +188,9 @@ export const App: React.FC = () => {
setTodos(updatedTodos);
} catch {
setError(ErrorMessages.UPDATE_TODOS);
setTimeout(() => setError(null), 3000);
const timer = setTimeout(() => setError(ErrorMessages.DEFAULT), 3000);

return () => clearTimeout(timer);
}
}, [todos, allCompleted]);

Expand All @@ -197,46 +207,31 @@ export const App: React.FC = () => {
);

if (hasErrors) {
setError('Unable to delete some completed todos');
setTimeout(() => {
setError(null);
}, 3000);
setError(ErrorMessages.DELETING_SOME_TODOS);
}
} catch {
setError('Unable to clear completed todos');
setTimeout(() => {
setError(null);
}, 3000);
setError(ErrorMessages.CLEAR_COMPLETED_TODOS);
}
}, [handleDeleteTodo, todos]);

const handleHideError = () => {
setError(null);
};

const handleCancelEdit = (event: React.KeyboardEvent) => {
if (event.key === 'Escape') {
setIsEditingId(null);
}
setError(ErrorMessages.DEFAULT);
};

useEffect(() => {
const fetchTodos = async () => {
try {
const data = await getTodos();

setTodos(data);
} catch (err) {
setError(ErrorMessages.LOAD_TODOS);
const timer = setTimeout(() => setError(null), 3000);

return () => clearTimeout(timer);
}
};

fetchTodos();
}, []);

useEffect(() => {
if (error) {
const timeout = setTimeout(() => {
setError(ErrorMessages.DEFAULT);
}, 3000);

return () => clearTimeout(timeout);
}
}, [error, ErrorMessages]);

Check warning on line 233 in src/App.tsx

View workflow job for this annotation

GitHub Actions / run_linter (20.x)

React Hook useEffect has an unnecessary dependency: 'ErrorMessages'. Either exclude it or remove the dependency array. Outer scope values like 'ErrorMessages' aren't valid dependencies because mutating them doesn't re-render the component

return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>
Expand All @@ -262,8 +257,9 @@ export const App: React.FC = () => {
onUpdateTitle={handleUpdateTodoTitle}
setError={setError}
isEditing={isEditingId === todo.id}
setIsEditing={setIsEditingId}
onDoubleClick={() => handleDoubleClick(todo.id)}
onCancelEdit={event => handleCancelEdit(event)}
error={error}
/>
))}

Expand All @@ -275,6 +271,7 @@ export const App: React.FC = () => {
onUpdateTitle={handleUpdateTodoTitle}
setError={setError}
isAdding={isAdding}
error={error}
/>
)}

Expand Down
7 changes: 4 additions & 3 deletions src/components/ErrorNotification.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import { FC } from 'react';
import classNames from 'classnames';
import { ErrorMessages } from '../types/enums';

interface ErrorNotificationProps {
error: string | null;
error: ErrorMessages;
onHideError: () => void;
}

export const ErrorNotification: React.FC<ErrorNotificationProps> = ({
export const ErrorNotification: FC<ErrorNotificationProps> = ({
error,
onHideError,
}) => {
Expand Down
9 changes: 5 additions & 4 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import { FC } from 'react';
import { FilterStates } from '../types/enums';
import { Todo } from '../types/Todo';

Expand All @@ -9,22 +9,23 @@ interface FooterProps {
onClearCompleted: () => void;
}

export const Footer: React.FC<FooterProps> = ({
export const Footer: FC<FooterProps> = ({
todos,
filter,
setFilter,
onClearCompleted,
}) => {
const activeTodos = todos.filter(todo => !todo.completed).length;
const filterValues = Object.values(FilterStates);

return (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{`${activeTodos} items left`}
{activeTodos} items left
</span>

<nav className="filter" data-cy="Filter">
{Object.values(FilterStates).map(state => (
{filterValues.map(state => (
<a
key={state}
href={`#/${state}`}
Expand Down
10 changes: 6 additions & 4 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import { FC } from 'react';
import classNames from 'classnames';
import { ErrorMessages } from '../types/enums';

Expand All @@ -10,10 +10,10 @@ interface HeaderProps {
setTitle: (value: string) => void;
todoCount: number;
isAdding: boolean;
setError: (message: string | null) => void;
setError: (message: ErrorMessages) => void;
}

export const Header: React.FC<HeaderProps> = ({
export const Header: FC<HeaderProps> = ({
allCompleted,
onAddTodo,
onToggleAllTodos,
Expand All @@ -29,7 +29,9 @@ export const Header: React.FC<HeaderProps> = ({
await onAddTodo(title.trim());
} catch (error) {
setError(ErrorMessages.ADDING_TODOS);
setTimeout(() => setError(null), 3000);
const timer = setTimeout(() => setError(ErrorMessages.DEFAULT), 3000);

return () => clearTimeout(timer);
}
};

Expand Down
39 changes: 29 additions & 10 deletions src/components/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import React, { useRef, useState } from 'react';
import { FC, useEffect, useRef, useState } from 'react';
import { ErrorMessages } from '../types/enums';
import classNames from 'classnames';
import { Todo } from '../types/Todo';
import cn from 'classnames';

interface TodoItemProps {
todo: Todo;
onDelete: (id: number) => Promise<void>;
onToggleStatus: (id: number, completed: boolean) => Promise<void>;
onUpdateTitle: (id: number, title: string) => Promise<void>;
setError: (message: string | null) => void;
setError: (message: ErrorMessages) => void;
isAdding?: boolean;
isEditing?: boolean;
setIsEditing?: (isEditing: number | null) => void;
onDoubleClick?: () => void;
onCancelEdit?: (event: React.KeyboardEvent) => void;
error: ErrorMessages;
}

export const TodoItem: React.FC<TodoItemProps> = ({
export const TodoItem: FC<TodoItemProps> = ({
todo,
onDelete,
onToggleStatus,
Expand All @@ -25,7 +27,8 @@ export const TodoItem: React.FC<TodoItemProps> = ({
isAdding,
isEditing,
onDoubleClick,
onCancelEdit,
error,
setIsEditing,
}) => {
const [newTitle, setNewTitle] = useState(todo.title);
const [isUpdating, setIsUpdating] = useState(false);
Expand All @@ -39,7 +42,6 @@ export const TodoItem: React.FC<TodoItemProps> = ({
await onDelete(todo.id);
} catch {
setError(ErrorMessages.DELETING_TODOS);
setTimeout(() => setError(null), 3000);
} finally {
setIsDeletingItem(false);
}
Expand All @@ -51,7 +53,6 @@ export const TodoItem: React.FC<TodoItemProps> = ({
await onToggleStatus(todo.id, !todo.completed);
} catch {
setError(ErrorMessages.UPDATE_TODOS);
setTimeout(() => setError(null), 3000);
} finally {
setIsUpdating(false);
}
Expand All @@ -65,18 +66,36 @@ export const TodoItem: React.FC<TodoItemProps> = ({
await onUpdateTitle(todo.id, newTitle.trim());
} catch {
setError(ErrorMessages.UPDATE_TODOS);
setTimeout(() => setError(null), 3000);
} finally {
setIsUpdating(false);
}
};

const handleCancelEdit = (event: React.KeyboardEvent) => {
if (event.key === 'Escape') {
handleEditSubmit(event);
if (setIsEditing) {
setIsEditing(null);
}
}
};

const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
handleEditSubmit(event);
};

useEffect(() => {
if (error) {
const timeout = setTimeout(() => {
setError(ErrorMessages.DEFAULT);
}, 3000);

return () => clearTimeout(timeout);
}
}, [error, ErrorMessages]);

Check warning on line 95 in src/components/TodoItem.tsx

View workflow job for this annotation

GitHub Actions / run_linter (20.x)

React Hook useEffect has a missing dependency: 'setError'. Either include it or remove the dependency array. Outer scope values like 'ErrorMessages' aren't valid dependencies because mutating them doesn't re-render the component

return (
<div data-cy="Todo" className={`todo ${todo.completed ? 'completed' : ''}`}>
<div data-cy="Todo" className={cn('todo', { completed: todo.completed })}>
<label className="todo__status-label">
<input
data-cy="TodoStatus"
Expand All @@ -96,7 +115,7 @@ export const TodoItem: React.FC<TodoItemProps> = ({
value={newTitle}
className="todo__title-field"
onChange={e => setNewTitle(e.target.value)}
onKeyDown={onCancelEdit}
onKeyDown={handleCancelEdit}
onBlur={handleBlur}
disabled={isUpdating}
autoFocus
Expand Down
Loading

0 comments on commit 6bfe432

Please sign in to comment.