diff --git a/README.md b/README.md index d3c3756ab..b2f9d9493 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://VikaChereushenko.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 72468e910..1d293f739 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,33 +9,18 @@ import { TodoList } from './components/TodoList/TodoList'; import { TempTodo } from './components/TempTodo/TempTodo'; import { Footer } from './components/Footer/Fotter'; import { Error } from './components/Error/Erros'; -import { getTodos, addTodo, deleteTodo, updateTodo } from './api/todos'; +import { getTodos } from './api/todos'; +import { filterTodos } from './components/Helpers/Helpers'; import { Todo } from './types/Todo'; import { TodoStatus } from './types/Status'; -function filterTodos(todos: Todo[], status: TodoStatus) { - const todosCopy = [...todos]; - - switch (status) { - case TodoStatus.active: - return todosCopy.filter(todo => !todo.completed); - case TodoStatus.completed: - return todosCopy.filter(todo => todo.completed); - case TodoStatus.all: - return todosCopy; - } -} - export const App: React.FC = () => { const [todos, setTodos] = useState([]); const [errorMessage, setErrorMessage] = useState(''); const [status, setStatus] = useState(TodoStatus.all); - const [title, setTitle] = useState(''); - const [loading, setLoading] = useState(false); const [tempTodo, setTempTodo] = useState(null); - const [processedIs, setProcessedIs] = useState([]); - const areAllTodosCompleted = todos.every(todo => todo.completed); + const [processedIds, setprocessedIds] = useState([]); const noTodos = todos.length === 0; const filteredTodos = filterTodos(todos, status); @@ -45,204 +30,6 @@ export const App: React.FC = () => { .catch(() => setErrorMessage('Unable to load todos')); }, []); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - const normalizeTitle = title.trim(); - - if (!normalizeTitle) { - setErrorMessage('Title should not be empty'); - - return; - } - - setLoading(true); - const newTodo = { - userId: 2042, - title: normalizeTitle, - completed: false, - }; - - setTempTodo({ - id: 0, - ...newTodo, - }); - - addTodo(newTodo) - .then(response => { - setTitle(''); - setTodos(existing => [...existing, response]); - setLoading(false); - }) - .catch(() => setErrorMessage('Unable to add a todo')) - .finally(() => { - setLoading(false); - setTempTodo(null); - }); - }; - - const handleDeleteOneTodo = (id: number) => { - setLoading(true); - setProcessedIs(existing => [...existing, id]); - deleteTodo(id) - .then(() => { - setTodos(existing => existing.filter(current => current.id !== id)); - }) - .catch(() => setErrorMessage('Unable to delete a todo')) - .finally(() => { - setProcessedIs([]); - setLoading(false); - }); - }; - - const handleDeleteCompletedTodos = () => { - todos.forEach(todo => { - if (todo.completed) { - setLoading(true); - setProcessedIs(existing => [...existing, todo.id]); - deleteTodo(todo.id) - .then(() => - setTodos(existing => - existing.filter(current => current.id !== todo.id), - ), - ) - .catch(() => setErrorMessage('Unable to delete a todo')) - .finally(() => { - setLoading(false); - setProcessedIs(existing => existing.filter(id => id !== todo.id)); - }); - } - }); - }; - - const handleStatusUpdate = (id: number) => { - setProcessedIs(existing => [...existing, id]); - - const changeItem = todos.find(todo => todo.id === id); - - if (changeItem) { - const toUpdate = { completed: !changeItem.completed }; - - updateTodo(id, toUpdate) - .then(() => { - setTodos(existing => - existing.map(el => - el.id === id ? { ...el, completed: toUpdate.completed } : el, - ), - ); - }) - .catch(() => setErrorMessage('Unable to update a todo')) - .finally(() => { - setProcessedIs([]); - }); - } - }; - - const handleTotalStatusUpdate = () => { - if (!areAllTodosCompleted) { - todos.forEach(todo => { - if (!todo.completed) { - setLoading(true); - setProcessedIs(existing => [...existing, todo.id]); - const toUpdate = { completed: true }; - - updateTodo(todo.id, toUpdate) - .then(() => { - setTodos(existing => - existing.map(el => - el.id === todo.id - ? { ...el, completed: toUpdate.completed } - : el, - ), - ); - }) - .catch(() => setErrorMessage('Unable to update a todo')) - .finally(() => { - setLoading(false); - setProcessedIs(existing => existing.filter(id => id !== todo.id)); - }); - } - }); - } - - if (areAllTodosCompleted) { - todos.forEach(todo => { - setLoading(true); - setProcessedIs(existing => [...existing, todo.id]); - const toUpdate = { completed: false }; - - updateTodo(todo.id, toUpdate) - .then(() => { - setTodos(existing => - existing.map(el => - el.id === todo.id - ? { ...el, completed: toUpdate.completed } - : el, - ), - ); - }) - .catch(() => setErrorMessage('Unable to update a todo')) - .finally(() => { - setLoading(false); - setProcessedIs(existing => existing.filter(id => id !== todo.id)); - }); - }); - } - }; - - const handleTitleUpdate = ( - id: number, - newTitle: string, - setTitleBeingUpdated: React.Dispatch>, - isTitleChanged: boolean, - e?: React.FormEvent, - ) => { - e?.preventDefault(); - - const normalizeNewTitle = newTitle.trim(); - - if (!normalizeNewTitle) { - handleDeleteOneTodo(id); - - return; - } - - if (!isTitleChanged) { - setTitleBeingUpdated(false); - - return; - } - - const changeItem = todos.find(todo => todo.id === id); - const toUpdate = { title: normalizeNewTitle }; - - if (changeItem) { - setProcessedIs(existing => [...existing, id]); - updateTodo(id, toUpdate) - .then(() => { - setTodos(existing => - existing.map(el => - el.id === id ? { ...el, title: toUpdate.title } : el, - ), - ); - setTitleBeingUpdated(false); - }) - .catch(() => setErrorMessage('Unable to update a todo')) - .finally(() => { - setProcessedIs([]); - }); - } - }; - - const handleKeyUp = ( - event: React.KeyboardEvent, - setTitleBeingUpdated: React.Dispatch>, - ) => { - if (event.key === 'Escape') { - setTitleBeingUpdated(false); - } - }; - if (!USER_ID) { return ; } @@ -253,32 +40,32 @@ export const App: React.FC = () => {
setTitle(value)} - onTotalStatusUpdate={handleTotalStatusUpdate} + todoList={todos} + onError={setErrorMessage} + updateTodoList={setTodos} + updateTempTodo={setTempTodo} + updateProcessedIds={setprocessedIds} /> {tempTodo && } {!noTodos && (
setStatus(value)} - clearCompletedTodos={handleDeleteCompletedTodos} + updateProcessedIds={setprocessedIds} + onError={setErrorMessage} /> )}
diff --git a/src/components/Error/Erros.tsx b/src/components/Error/Erros.tsx index 8d8065c91..d24c2a386 100644 --- a/src/components/Error/Erros.tsx +++ b/src/components/Error/Erros.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; type Props = { errorMessage: string; - hideError: (arg: string) => void; + hideError: (errorMessage: string) => void; }; export const Error: React.FC = ({ errorMessage, hideError }) => { diff --git a/src/components/Footer/Fotter.tsx b/src/components/Footer/Fotter.tsx index 54de8b45a..ffc1ececb 100644 --- a/src/components/Footer/Fotter.tsx +++ b/src/components/Footer/Fotter.tsx @@ -1,29 +1,58 @@ -import React from 'react'; +import React, { useCallback } from 'react'; +import { useMemo } from 'react'; import classNames from 'classnames'; +import { deleteTodo } from '../../api/todos'; +import { capitalizeFirstLetter, filterOptions } from '../Helpers/Helpers'; + import { Todo } from '../../types/Todo'; import { TodoStatus } from '../../types/Status'; type Props = { - todos: Todo[]; + todoList: Todo[]; + updateTodolist: React.Dispatch>; status: TodoStatus; - onStatusChange: (arg: TodoStatus) => void; - clearCompletedTodos: () => void; + onStatusChange: (status: TodoStatus) => void; + updateProcessedIds: React.Dispatch>; + onError: React.Dispatch>; }; export const Footer: React.FC = ({ - todos, + todoList, + updateTodolist, status, onStatusChange, - clearCompletedTodos, + updateProcessedIds, + onError, }) => { - const activeTodos = todos.filter(todo => !todo.completed); - const isAnyCompleted = todos.some(todo => todo.completed); - const filterOptions = Object.values(TodoStatus); + const activeTodos = useMemo( + () => todoList.filter(todo => !todo.completed), + [todoList], + ); + const isAnyCompleted = useMemo( + () => todoList.some(todo => todo.completed), + [todoList], + ); - const capitalizeFirstLetter = (value: TodoStatus) => { - return `${value.charAt(0).toUpperCase()}${value.slice(1)}`; - }; + const handleDeleteCompletedTodos = useCallback(() => { + todoList.forEach(todo => { + if (todo.completed) { + updateProcessedIds(existing => [...existing, todo.id]); + deleteTodo(todo.id) + .then(() => + updateTodolist(existing => + existing.filter(current => current.id !== todo.id), + ), + ) + .catch(() => onError('Unable to delete a todo')) + .finally(() => { + updateProcessedIds(existing => + existing.filter(id => id !== todo.id), + ); + }); + } + }); + }, [todoList]); return (