From a7913beff9415a2ecee1a0fd78b5049e80437f14 Mon Sep 17 00:00:00 2001 From: Mariia Bulchak Date: Wed, 23 Oct 2024 14:24:31 +0300 Subject: [PATCH 1/3] file Header, for checking the focus on update while offline for mentor --- src/App.tsx | 169 +++++++++++-- src/api/index.ts | 1 + src/api/todos.ts | 25 ++ .../ErrorNotification/ErrorNotification.tsx | 42 ++++ src/components/ErrorNotification/index.ts | 1 + src/components/Footer/Footer.tsx | 69 ++++++ src/components/Footer/index.ts | 1 + src/components/Header/Header.tsx | 141 +++++++++++ src/components/Header/index.ts | 1 + src/components/TodoItem/TodoItem.tsx | 224 ++++++++++++++++++ src/components/TodoItem/index.ts | 1 + src/components/TodoList/TodoList.tsx | 72 ++++++ src/components/TodoList/index.ts | 1 + src/types/Errors.ts | 8 + src/types/Filters.ts | 5 + src/types/Todo.ts | 6 + src/utils/fetchClient.ts | 48 ++++ src/utils/getFilteredTodosByStatus.ts | 13 + 18 files changed, 809 insertions(+), 19 deletions(-) create mode 100644 src/api/index.ts create mode 100644 src/api/todos.ts create mode 100644 src/components/ErrorNotification/ErrorNotification.tsx create mode 100644 src/components/ErrorNotification/index.ts create mode 100644 src/components/Footer/Footer.tsx create mode 100644 src/components/Footer/index.ts create mode 100644 src/components/Header/Header.tsx create mode 100644 src/components/Header/index.ts create mode 100644 src/components/TodoItem/TodoItem.tsx create mode 100644 src/components/TodoItem/index.ts create mode 100644 src/components/TodoList/TodoList.tsx create mode 100644 src/components/TodoList/index.ts create mode 100644 src/types/Errors.ts create mode 100644 src/types/Filters.ts create mode 100644 src/types/Todo.ts create mode 100644 src/utils/fetchClient.ts create mode 100644 src/utils/getFilteredTodosByStatus.ts diff --git a/src/App.tsx b/src/App.tsx index 81e011f432..0a2cc2ed63 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,157 @@ -/* eslint-disable max-len */ -/* eslint-disable jsx-a11y/control-has-associated-label */ -import React from 'react'; -import { UserWarning } from './UserWarning'; +import React, { useEffect, useState } from 'react'; +import { Header } from './components/Header'; +import { TodoList } from './components/TodoList'; +import { Footer } from './components/Footer'; +import { ErrorNotification } from './components/ErrorNotification'; -const USER_ID = 0; +import { getFilteredTodosByStatus } from './utils/getFilteredTodosByStatus'; + +import { deleteTodo, getTodos } from './api/todos'; + +import { Todo } from './types/Todo'; +import { Errors } from './types/Errors'; +import { Filter } from './types/Filters'; +import { todosService } from './api'; export const App: React.FC = () => { - if (!USER_ID) { - return ; - } + const [todos, setTodos] = useState([]); + const [filter, setFilter] = useState(Filter.All); + + const [errorMessage, setErrorMessage] = useState(Errors.DEFAULT); + const [isLoading, setIsLoading] = useState(false); + + // const [isEditing, setIsEditing] = useState(false); + + const [tempTodo, setTempTodo] = useState(null); + + const [loadingTodoId, setLoadingTodoId] = useState([]); + + const filteredTodos = getFilteredTodosByStatus(todos, filter); + + const countActiveTodos = todos.reduce((accum, todo) => { + return !todo.completed ? accum + 1 : accum; + }, 0); + + const handleDeleteTodo = (id: number) => { + setIsLoading(true); + setLoadingTodoId(currentIds => [...currentIds, id]); + + deleteTodo(id) + .then(() => { + setLoadingTodoId(currentIds => + currentIds.filter(currId => currId !== id), + ); + + setTodos(currentTodos => + currentTodos.filter(currentTodo => currentTodo.id !== id), + ); + // setIsLoading(false); + }) + .catch(error => { + // setIsLoading(false); + setErrorMessage(Errors.DELETE_TODO); + throw error; + }) + .finally(() => { + setIsLoading(false); + setLoadingTodoId(currentIds => + currentIds.filter(currId => currId !== id), + ); + }); + }; + + // create new func for all todos using promise all + // change setTodos + + const handleUpdateTodo = (todoToUpdate: Todo) => { + setIsLoading(true); + setLoadingTodoId(currentIds => [...currentIds, todoToUpdate.id]); + + todosService + .updateTodo(todoToUpdate) + .then(updatedTodo => { + setLoadingTodoId(currentIds => + currentIds.filter(currId => currId !== todoToUpdate.id), + ); + + setTodos(currentTodos => { + return currentTodos.map(defTodo => + defTodo.id === updatedTodo.id ? updatedTodo : defTodo, + ); + }); + }) + .catch(error => { + setErrorMessage(Errors.UPDATE_TODO); + throw error; + }) + .finally(() => { + setIsLoading(false); + + // setLoadingTodoId(currentIds => + // currentIds.filter(currId => currId !== todoToUpdate.id), + // ); + }); + }; + + useEffect(() => { + getTodos() + .then(setTodos) + .catch(() => { + setErrorMessage(Errors.LOADING); + }); + }, []); return ( -
-

- Copy all you need from the prev task: -
- - React Todo App - Add and Delete - -

- -

Styles are already copied

-
+
+

todos

+ +
+
+ + {todos.length > 0 && ( + <> + + +
+ + )} +
+ + +
); }; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000000..8de6b72e1b --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1 @@ +export * as todosService from './todos'; diff --git a/src/api/todos.ts b/src/api/todos.ts new file mode 100644 index 0000000000..cb30195503 --- /dev/null +++ b/src/api/todos.ts @@ -0,0 +1,25 @@ +import { Todo } from '../types/Todo'; +import { client } from '../utils/fetchClient'; + +export const USER_ID = 1613; + +export const getTodos = () => { + return client.get(`/todos?userId=${USER_ID}`); +}; + +export const createTodo = ({ userId, title, completed }: Omit) => { + return client.post('/todos', { userId, title, completed }); +}; + +export const deleteTodo = (todoId: number) => { + return client.delete(`/todos/${todoId}`); +}; + +export const updateTodo = ({ + id, + userId, + title, + completed, +}: Todo): Promise => { + return client.patch(`/todos/${id}`, { userId, title, completed }); +}; diff --git a/src/components/ErrorNotification/ErrorNotification.tsx b/src/components/ErrorNotification/ErrorNotification.tsx new file mode 100644 index 0000000000..1e1bfb343f --- /dev/null +++ b/src/components/ErrorNotification/ErrorNotification.tsx @@ -0,0 +1,42 @@ +import cn from 'classnames'; +import { Dispatch, SetStateAction, useEffect } from 'react'; +import { Errors } from '../../types/Errors'; + +type Props = { + errorMessage: string; + setErrorMessage: Dispatch>; +}; + +export const ErrorNotification: React.FC = ({ + errorMessage, + setErrorMessage, +}) => { + useEffect(() => { + let timer: NodeJS.Timeout; + + if (errorMessage) { + timer = setTimeout(() => setErrorMessage(Errors.DEFAULT), 3000); + } + + return () => { + clearTimeout(timer); + }; + }, [setErrorMessage, errorMessage]); + + return ( +
+
+ ); +}; diff --git a/src/components/ErrorNotification/index.ts b/src/components/ErrorNotification/index.ts new file mode 100644 index 0000000000..8cb4787920 --- /dev/null +++ b/src/components/ErrorNotification/index.ts @@ -0,0 +1 @@ +export * from './ErrorNotification'; diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx new file mode 100644 index 0000000000..be83d95205 --- /dev/null +++ b/src/components/Footer/Footer.tsx @@ -0,0 +1,69 @@ +import { Filter } from '../../types/Filters'; +import cn from 'classnames'; +import { Todo } from '../../types/Todo'; + +type Props = { + countActiveTodos: number; + filter: Filter; + setFilter: (filter: Filter) => void; + todos: Todo[]; + handleDeleteTodo: (id: number) => void; +}; + +export const Footer: React.FC = ({ + countActiveTodos, + filter, + setFilter, + todos, + handleDeleteTodo, +}) => { + const filterOptionName = Object.values(Filter); + + const completedTodos = todos.filter(todo => todo.completed); + + const deleteAllCompletedTodos = () => { + completedTodos.map(todo => handleDeleteTodo(todo.id)); + }; + + return ( +
+ + {`${countActiveTodos} items left`} + + + + + {/* this button should be disabled if there are no completed todos */} + +
+ ); +}; diff --git a/src/components/Footer/index.ts b/src/components/Footer/index.ts new file mode 100644 index 0000000000..ddcc5a9cd1 --- /dev/null +++ b/src/components/Footer/index.ts @@ -0,0 +1 @@ +export * from './Footer'; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx new file mode 100644 index 0000000000..d88f27cefa --- /dev/null +++ b/src/components/Header/Header.tsx @@ -0,0 +1,141 @@ +import React, { + Dispatch, + SetStateAction, + useEffect, + useRef, + useState, +} from 'react'; +import cn from 'classnames'; +import { Todo } from '../../types/Todo'; +import { todosService } from '../../api'; +import { USER_ID } from '../../api/todos'; +import { Errors } from '../../types/Errors'; + +type Props = { + tempTodo: Todo | null; + setTempTodo: Dispatch>; + isLoading: boolean; + setIsLoading: Dispatch>; + todos: Todo[]; + setTodos: Dispatch>; + setErrorMessage: (Error: Errors) => void; + errorMessage: string; + handleUpdateTodo: (todoToUpdate: Todo) => void; + // handleToggleAll: (todos: Todo[]) => Todo[]; +}; + +export const Header: React.FC = ({ + // tempTodo, + setTempTodo, + isLoading, + setIsLoading, + setErrorMessage, + todos, + setTodos, + errorMessage, + handleUpdateTodo, + // handleToggleAll, +}) => { + const field = useRef(null); + const [newTitle, setNewTitle] = useState(''); + + const handleTitle = (event: React.ChangeEvent) => { + setNewTitle(event.target.value); + }; + + function addTodo({ userId, title, completed }: Omit) { + const titleTrim = title.trim(); + + if (!titleTrim) { + setIsLoading(false); + setErrorMessage(Errors.TITLE); + + return; + } + + const newTodo = { + userId, + title: titleTrim, + completed, + }; + + setTempTodo({ + id: 0, + ...newTodo, + }); + + return todosService + .createTodo(newTodo) + .then(defTodo => { + setTodos(currentTodos => [...currentTodos, defTodo]); + setNewTitle(''); + }) + .catch(() => setErrorMessage(Errors.ADD_TODO)) + .finally(() => { + field.current?.focus(); + setTempTodo(null); + setIsLoading(false); + }); + } + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + setIsLoading(true); + + addTodo({ + userId: USER_ID, + title: newTitle, + completed: false, + }); + }; + + const allCompletedTodos = todos.every(todo => todo.completed); + + function handleToggleAll(defTodos: Todo[]) { + if (allCompletedTodos) { + defTodos.forEach(defTodo => + handleUpdateTodo({ ...defTodo, completed: false }), + ); + } else { + defTodos.forEach(defTodo => + handleUpdateTodo({ ...defTodo, completed: true }), + ); + } + } + + useEffect(() => { + // if (!setLoadingTodoId) + field.current?.focus(); + // }, [todos.length, tempTodo]); + // }, [isLoading, errorMessage]); + }, [todos]); + + return ( +
+ {/* this button should have `active` class only if all todos are completed */} + {todos.length > 0 && ( +
+ ); +}; diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts new file mode 100644 index 0000000000..266dec8a1b --- /dev/null +++ b/src/components/Header/index.ts @@ -0,0 +1 @@ +export * from './Header'; diff --git a/src/components/TodoItem/TodoItem.tsx b/src/components/TodoItem/TodoItem.tsx new file mode 100644 index 0000000000..d84e44dbfd --- /dev/null +++ b/src/components/TodoItem/TodoItem.tsx @@ -0,0 +1,224 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/control-has-associated-label */ + +// import { useState } from 'react'; +// import { todosService } from '../../api'; +import { useEffect, useRef, useState } from 'react'; +import { Errors } from '../../types/Errors'; +import { Todo } from '../../types/Todo'; +import cn from 'classnames'; + +type Props = { + todo: Todo; + setTodos: React.Dispatch>; + setErrorMessage: React.Dispatch>; + isLoading: boolean; + setIsLoading: React.Dispatch>; + deleteTodoId: number[]; + setDeleteTodoId: React.Dispatch>; + handleDeleteTodo: (id: number) => void; + handleUpdateTodo: (todoToUpdate: Todo) => void; + // isEditing: boolean; + // setIsEditing: React.Dispatch>; +}; + +export const TodoItem: React.FC = ({ + todo, + // setTodos, + isLoading, + // setIsLoading, + deleteTodoId, + // setErrorMessage, + handleDeleteTodo, + handleUpdateTodo, + // isEditing, + // setIsEditing, +}) => { + // const [checked, setChecked] = useState(false); + + // const handleChangeChecked + + // const handleUpdateTodo = (todoToUpdate: Todo) => { + // setIsLoading(true); + + // todosService + // .updateTodo(todoToUpdate) + // .then(updatedTodo => { + // // console.log(updatedTodo); + + // // setTodos((currentTodos: Todo[]): Todo[] => { + // setTodos(currentTodos => { + // // const newTodos = [...currentTodos]; + // // const index = newTodos.findIndex( + // // defTodo => defTodo.id === todoToUpdate.id, + // // ); + + // // return newTodos.splice(index, 1, updatedTodo); + + // return currentTodos.map(defTodo => + // defTodo.id === updatedTodo.id ? updatedTodo : defTodo, + // ); + // }); + // }) + // .catch(() => setErrorMessage(Errors.UPDATE_TODO)) + // .finally(() => setIsLoading(false)); + // }; + + const [newTitle, setNewTitle] = useState(todo.title); + const [isEditing, setIsEditing] = useState(false); + + const inputRef = useRef(null); + + const handleSaveChanges = (event: React.FormEvent) => { + event.preventDefault(); + // setIsLoading(true); + + const preparedTitle = newTitle.trim(); + + if (!preparedTitle) { + handleDeleteTodo(todo.id); + + return; + } + + if (preparedTitle === todo.title) { + setIsEditing(false); + + return; + } + + handleUpdateTodo({ ...todo, title: newTitle }); + // .then(() => { + // setIsLoading(false); + // setIsEditing(false); + // }); + + + // прокинути errorMessage i робити !setIsEditing(false) + // setIsLoading(false); + + setIsEditing(false); + }; + + const handleTitleChange = (event: React.ChangeEvent) => { + setNewTitle(event.target.value); + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [isEditing]); + + return ( +
+ + + {/* // state, = for togle all is active' => and because of this */} + {/* if one of them is not active change todo.completed */} + + {isEditing ? ( +
+ { + // console.log(event); + + if (event.key === 'Escape') { + setIsEditing(false); + setNewTitle(todo.title); + } + }} + /> +
+ ) : ( + <> + setIsEditing(true)} + data-cy="TodoTitle" + className="todo__title" + > + {todo.title} + + + + + )} + + {/* + {todo.title} + + + + + {false && ( +
+ +
+ )} */} + +
+
+
+
+
+ ); +}; diff --git a/src/components/TodoItem/index.ts b/src/components/TodoItem/index.ts new file mode 100644 index 0000000000..21f4abac39 --- /dev/null +++ b/src/components/TodoItem/index.ts @@ -0,0 +1 @@ +export * from './TodoItem'; diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx new file mode 100644 index 0000000000..714169a471 --- /dev/null +++ b/src/components/TodoList/TodoList.tsx @@ -0,0 +1,72 @@ +import { Errors } from '../../types/Errors'; +import { Todo } from '../../types/Todo'; +import { TodoItem } from '../TodoItem'; + +type Props = { + todos: Todo[]; + tempTodo: Todo | null; + isLoading: boolean; + setIsLoading: React.Dispatch>; + setTodos: React.Dispatch>; + setErrorMessage: React.Dispatch>; + loadingTodoId: number[]; + setDeleteTodoId: React.Dispatch>; + handleDeleteTodo: (id: number) => void; + handleUpdateTodo: (todoToUpdate: Todo) => void; + // isEditing: boolean; + // setIsEditing: React.Dispatch>; +}; + +export const TodoList: React.FC = ({ + todos, + tempTodo, + isLoading, + setIsLoading, + setTodos, + setErrorMessage, + loadingTodoId: deleteTodoId, + setDeleteTodoId, + handleDeleteTodo, + handleUpdateTodo, + // isEditing, + // setIsEditing, +}) => { + return ( +
+ {todos.map(todo => { + return ( + + ); + })} + + {tempTodo && ( + + )} +
+ ); +}; diff --git a/src/components/TodoList/index.ts b/src/components/TodoList/index.ts new file mode 100644 index 0000000000..f239f43459 --- /dev/null +++ b/src/components/TodoList/index.ts @@ -0,0 +1 @@ +export * from './TodoList'; diff --git a/src/types/Errors.ts b/src/types/Errors.ts new file mode 100644 index 0000000000..642ad44509 --- /dev/null +++ b/src/types/Errors.ts @@ -0,0 +1,8 @@ +export enum Errors { + DEFAULT = '', + LOADING = 'Unable to load todos', + TITLE = 'Title should not be empty', + ADD_TODO = 'Unable to add a todo', + DELETE_TODO = 'Unable to delete a todo', + UPDATE_TODO = 'Unable to update a todo', +} diff --git a/src/types/Filters.ts b/src/types/Filters.ts new file mode 100644 index 0000000000..66887875b7 --- /dev/null +++ b/src/types/Filters.ts @@ -0,0 +1,5 @@ +export enum Filter { + All = 'All', + Active = 'Active', + Completed = 'Completed', +} diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 0000000000..3f52a5fdde --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,6 @@ +export interface Todo { + id: number; + userId: number; + title: string; + completed: boolean; +} diff --git a/src/utils/fetchClient.ts b/src/utils/fetchClient.ts new file mode 100644 index 0000000000..53fa1f93ba --- /dev/null +++ b/src/utils/fetchClient.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +const BASE_URL = 'https://mate.academy/students-api'; + +// https://mate.academy/students-api/todos?userId=1614 + +// returns a promise resolved after a given delay +function wait(delay: number) { + return new Promise(resolve => { + setTimeout(resolve, delay); + }); +} + +// To have autocompletion and avoid mistypes +type RequestMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE'; + +function request( + url: string, + method: RequestMethod = 'GET', + data: any = null, // we can send any data to the server +): Promise { + const options: RequestInit = { method }; + + if (data) { + // We add body and Content-Type only for the requests with data + options.body = JSON.stringify(data); + options.headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + } + + // DON'T change the delay it is required for tests + return wait(100) + .then(() => fetch(BASE_URL + url, options)) + .then(response => { + if (!response.ok) { + throw new Error(); + } + + return response.json(); + }); +} + +export const client = { + get: (url: string) => request(url), + post: (url: string, data: any) => request(url, 'POST', data), + patch: (url: string, data: any) => request(url, 'PATCH', data), + delete: (url: string) => request(url, 'DELETE'), +}; diff --git a/src/utils/getFilteredTodosByStatus.ts b/src/utils/getFilteredTodosByStatus.ts new file mode 100644 index 0000000000..289776cb15 --- /dev/null +++ b/src/utils/getFilteredTodosByStatus.ts @@ -0,0 +1,13 @@ +import { Filter } from '../types/Filters'; +import { Todo } from '../types/Todo'; + +export const getFilteredTodosByStatus = (todos: Todo[], filter: Filter) => { + switch (filter) { + case Filter.Active: + return todos.filter(todo => !todo.completed); + case Filter.Completed: + return todos.filter(todo => todo.completed); + default: + return todos; + } +}; From 42def516b028b4ab54003781e74dd137333c777f Mon Sep 17 00:00:00 2001 From: Mariia Bulchak Date: Thu, 24 Oct 2024 09:31:22 +0300 Subject: [PATCH 2/3] add solution --- README.md | 2 +- src/App.tsx | 49 +++------ src/components/Header/Header.tsx | 41 ++++---- src/components/TodoItem/TodoItem.tsx | 149 ++++++--------------------- src/components/TodoList/TodoList.tsx | 45 +++----- 5 files changed, 80 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index d3c3756ab9..8ee4e9e827 100644 --- a/README.md +++ b/README.md @@ -47,4 +47,4 @@ Implement the ability to edit a todo title on double click: - Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline). - Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript). -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_todo-app-with-api/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://mbulchak.github.io/react_todo-app-with-api/) and add it to the PR description. diff --git a/src/App.tsx b/src/App.tsx index 0a2cc2ed63..f5d13a66be 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,8 +6,6 @@ import { ErrorNotification } from './components/ErrorNotification'; import { getFilteredTodosByStatus } from './utils/getFilteredTodosByStatus'; -import { deleteTodo, getTodos } from './api/todos'; - import { Todo } from './types/Todo'; import { Errors } from './types/Errors'; import { Filter } from './types/Filters'; @@ -18,9 +16,8 @@ export const App: React.FC = () => { const [filter, setFilter] = useState(Filter.All); const [errorMessage, setErrorMessage] = useState(Errors.DEFAULT); - const [isLoading, setIsLoading] = useState(false); - // const [isEditing, setIsEditing] = useState(false); + const [editedTodo, setEditedTodo] = useState(null); const [tempTodo, setTempTodo] = useState(null); @@ -33,10 +30,10 @@ export const App: React.FC = () => { }, 0); const handleDeleteTodo = (id: number) => { - setIsLoading(true); setLoadingTodoId(currentIds => [...currentIds, id]); - deleteTodo(id) + todosService + .deleteTodo(id) .then(() => { setLoadingTodoId(currentIds => currentIds.filter(currId => currId !== id), @@ -45,56 +42,46 @@ export const App: React.FC = () => { setTodos(currentTodos => currentTodos.filter(currentTodo => currentTodo.id !== id), ); - // setIsLoading(false); }) .catch(error => { - // setIsLoading(false); setErrorMessage(Errors.DELETE_TODO); throw error; }) .finally(() => { - setIsLoading(false); setLoadingTodoId(currentIds => currentIds.filter(currId => currId !== id), ); }); }; - // create new func for all todos using promise all - // change setTodos - const handleUpdateTodo = (todoToUpdate: Todo) => { - setIsLoading(true); setLoadingTodoId(currentIds => [...currentIds, todoToUpdate.id]); todosService .updateTodo(todoToUpdate) .then(updatedTodo => { - setLoadingTodoId(currentIds => - currentIds.filter(currId => currId !== todoToUpdate.id), - ); - setTodos(currentTodos => { return currentTodos.map(defTodo => defTodo.id === updatedTodo.id ? updatedTodo : defTodo, ); }); + + setEditedTodo(null); }) .catch(error => { setErrorMessage(Errors.UPDATE_TODO); throw error; }) .finally(() => { - setIsLoading(false); - - // setLoadingTodoId(currentIds => - // currentIds.filter(currId => currId !== todoToUpdate.id), - // ); + setLoadingTodoId(currentIds => + currentIds.filter(currId => currId !== todoToUpdate.id), + ); }); }; useEffect(() => { - getTodos() + todosService + .getTodos() .then(setTodos) .catch(() => { setErrorMessage(Errors.LOADING); @@ -109,15 +96,12 @@ export const App: React.FC = () => {
{todos.length > 0 && ( @@ -125,16 +109,11 @@ export const App: React.FC = () => {