-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Create todo app using API #767
base: master
Are you sure you want to change the base?
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.
src/App.tsx
Outdated
<NewTodo | ||
onWaiting={setTempTodo} | ||
/> |
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.
<NewTodo | |
onWaiting={setTempTodo} | |
/> | |
<NewTodo onWaiting={setTempTodo} /> |
Better write it in one line if there are < 3 props
src/components/NewTodo/NewTodo.tsx
Outdated
const { setErrorMessage, setIsErrorHidden } = useErrorMessage(); | ||
const [newTitle, setNewTitle] = useState(''); | ||
|
||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => { |
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.
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => { | |
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { |
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.
#767 (review) - it still doesn't work.
It would also be cool to disable the input at the time of adding a new todo:
https://gyazo.com/af36e4c7a97515b6933e89b1e45db1f5
It should also be taken into account that if the number of todos is 1, there should not be a plural in the text:
src/App.tsx
Outdated
.catch((error) => { | ||
setErrorMessage(JSON.parse(error.message).error); | ||
setIsErrorHidden(false); |
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 execute similar logic several times, you can move it to a separate handler:
function handleErrorMessage(error) {
setErrorMessage(JSON.parse(error.message).error);
setIsErrorHidden(false);
}
...
.catch(handleErrorMessage)
href={key === 'All' | ||
? '#/' | ||
: `#/${Filters[key][0].toLowerCase() + Filters[key].slice(1)}`} |
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 is better to put this logic in a separate variable for better readability
</span> | ||
|
||
<nav className="filter"> | ||
{(Object.keys(Filters) as Array<keyof typeof Filters>) |
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 can use Object.values right away.
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! let's improve your code!
-
focus is not set after double click (check the working example)
-
you can enable the editing of several todos at once (the editing mode is not turned off after unfocusing)
-
but in your case there is a request to a server with the same name -
input is not blocked when adding (during a request to the server), you can enter anything
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.
Great work 👍
Some functionality doesn't work properly, also left comments with suggestions, check them
src/App.tsx
Outdated
completedTodos.forEach(async ({ id }) => { | ||
setLoadingTodos(prev => [...prev, { todoId: id, isLoading: true }]); | ||
await deleteTodo(id); | ||
try { | ||
setTodos(prev => prev.filter((todo) => id !== todo.id)); | ||
} catch { | ||
handleShowError('Unable to delete todo'); | ||
} | ||
|
||
setLoadingTodos(prev => prev.filter(todo => todo.todoId !== id)); | ||
}); | ||
}; |
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.
async/await doesn't work properly in forEach
, use for of
loop or even better to use Promise.all
to handle multiple requests.
Also await deleteTodo(id);
should be in try
block if you want to catch errors.
Now it doesn't work as expected:
https://www.loom.com/share/78fa7ce58f064916927ee42bb3eb9fae?sid=b5c2e261-69ed-45dc-8dcb-4782a10e62fe
useEffect(() => { | ||
getTodos(USER_ID) | ||
.then(setTodos) | ||
.catch(() => { | ||
handleShowError('Unable to load todos'); | ||
}); | ||
}, []); |
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 a good idea to show the loader while fetching something from a server
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 a good idea to show the loader while fetching something from a server
I still don't see a loader, when user have bad internet connection they don't know is there any todos or no
src/App.tsx
Outdated
<div className={'modal-background' | ||
+ ' has-background-white-ter'} |
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.
<div className={'modal-background' | |
+ ' has-background-white-ter'} | |
<div className={'modal-background has-background-white-ter'} |
Why do you need to concat classes like 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.
Linter
src/App.tsx
Outdated
<TodoFilter | ||
todos={todos} | ||
filterParam={filterParam} | ||
onFilterChange={(newFilter) => setFilterParam(newFilter)} |
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.
onFilterChange={(newFilter) => setFilterParam(newFilter)} | |
onFilterChange={setFilterParam} |
|
||
export const ErrorMessageContextProvider: React.FC<Props> = ({ children }) => { | ||
const [errorMessage, setErrorMessage] = useState(''); | ||
const [isErrorHidden, setIsErrorHidden] = useState(true); |
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 isErrorHidden
, you can check if errorMessage
exists or not. An empty string is a falsy value.
https://developer.mozilla.org/en-US/docs/Glossary/Falsy
https://developer.mozilla.org/en-US/docs/Glossary/Truthy
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 want my error to disappear smoothly, if I just setErrorMessage('') it will remove text and after hides the block. Which seems incorrect for me.
https://drive.google.com/file/d/1murkaSWOv1Q86OYh3rrXKQ-SDNyChk88/view?usp=sharing
src/components/Todo/TodoTask.tsx
Outdated
'is-active': loadingTodos.find(({ todoId }) => ( | ||
todoId === todo.id))?.isLoading, |
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 can store only ids instead of objects with todoId
and isLoading
, and use method some
instead.
Also, move it to a vairable
onFilterChange, | ||
clearCompleted, | ||
}) => { | ||
const itemsLeft = `${countTodos(todos, false).length} item${countTodos(todos, false).length === 1 ? '' : 's'} left`; |
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.
countTodos(todos, false)
twice, create a variable
.map((value) => { | ||
const hrefValue = value === 'all' | ||
? '#/' | ||
: `#/${value[0].toLowerCase() + value.slice(1)}`; |
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.
Just toLowerCase for a whole string
src/components/TodoList/TodoList.tsx
Outdated
}) => { | ||
return ( | ||
<> | ||
{todos?.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.
{todos?.map(todo => ( | |
{todos.map(todo => ( |
todos
is required, you don't need to check
src/types/UpdateCompleted.ts
Outdated
export interface UpdateCompleted { | ||
completed: boolean, | ||
} |
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.
Don't create for every type a file, if it's related to Todo put it in the same file
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.
src/App.tsx
Outdated
case Filters.All: | ||
return todos; | ||
|
||
default: | ||
return todos; |
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.
case Filters.All: | |
return todos; | |
default: | |
return todos; | |
case Filters.All: | |
default: | |
return todos; |
src/App.tsx
Outdated
const { todos, setTodos } = useTodos(); | ||
|
||
const [tempTodo, setTempTodo] = useState<Todo | null>(null); | ||
const { setLoadingTodos } = useLoadingTodos(); | ||
const { | ||
errorMessage, | ||
isErrorHidden, | ||
setIsErrorHidden, | ||
handleShowError, | ||
} = useErrorMessage(); | ||
|
||
const [filterParam, setFilterParam] = useState(Filters.All); | ||
|
||
const clearCompletedTodos = async () => { | ||
const completedTodos = countTodos(todos, true).map(({ id }) => ( |
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.
Better to keep all state
s together, all your own hooks also together, all constants together and all useEffect
s also together
<section className="todoapp__main"> | ||
<TodoList todos={visibleTodos} /> | ||
|
||
{tempTodo && ( |
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.
Can this tempTodo be moved to a separate component?
src/App.tsx
Outdated
onClick={() => setIsErrorHidden(true)} | ||
aria-label="Delete Button" | ||
/> | ||
|
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.
src/components/NewTodo/NewTodo.tsx
Outdated
onWaiting({ | ||
id: 0, | ||
completed: false, | ||
title: newTitle, | ||
userId: USER_ID, | ||
}); |
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.
Can you create constant for this object, you use the same object below
src/components/NewTodo/NewTodo.tsx
Outdated
const changingTodos = countTodos(todos, false).length | ||
? countTodos(todos, false) |
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.
Not fixed
src/components/Todo/TodoTask.tsx
Outdated
setLoadingTodos(prev => prev.filter(id => ( | ||
id !== todo.id))); |
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.
setLoadingTodos(prev => prev.filter(id => ( | |
id !== todo.id))); | |
setLoadingTodos(prev => ( | |
prev.filter(id => (id !== todo.id) | |
)); |
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 same in other places
src/components/Todo/TodoTask.tsx
Outdated
'is-active': loadingTodos.some((id) => ( | ||
id === todo.id)), |
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 constant for this loadingTodos.some((id) => ( id === todo.id))
<button | ||
type="button" | ||
className="todoapp__clear-completed" | ||
disabled={countTodos(todos, true).length === 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.
Create constant for 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.
I don't see a point to create a constant for this, I don't reuse this array in another places in this component. Also as for me this code is easy to read.
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.
🥇
setLoadingTodos(prev => prev.filter(id => id !== todo.id)); | ||
}; | ||
|
||
const onDelete = async (todoId: number) => { |
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.
const onDelete = async (todoId: number) => { | |
const handleDeleteTodo = async (todoId: number) => { |
<div className={classNames( | ||
'notification is-danger is-light has-text-weight-normal', | ||
{ hidden: isErrorHidden }, | ||
)} | ||
> | ||
<button | ||
type="button" | ||
className="delete" | ||
onClick={() => setIsErrorHidden(true)} | ||
aria-label="Delete Button" | ||
/> | ||
{errorMessage} | ||
<br /> | ||
</div> |
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 also can be moved to separate 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.
Looks great 👍
Left a few suggestions to improve your solution.
useEffect(() => { | ||
getTodos(USER_ID) | ||
.then(setTodos) | ||
.catch(() => { | ||
handleShowError('Unable to load todos'); | ||
}); | ||
}, []); |
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 a good idea to show the loader while fetching something from a server
I still don't see a loader, when user have bad internet connection they don't know is there any todos or no
changingTodos.forEach(({ id }) => { | ||
setLoadingTodos(prev => [...prev, id]); | ||
}); |
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 add one todoId in every iteration, you can create the ids array with map
method and pass them to setLoadingTodos
updatedTodos.forEach((updatedTodo) => { | ||
setTodos(prev => prev.map((currentTodo) => { | ||
if (currentTodo.id === updatedTodo.id) { | ||
return updatedTodo; | ||
} | ||
|
||
return currentTodo; | ||
})); | ||
setLoadingTodos(prevLoads => ( | ||
prevLoads.filter(id => id !== updatedTodo.id))); | ||
}); |
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.
Here as well
const handleComplete = async (todoId: number, | ||
event: React.ChangeEvent<HTMLInputElement>) => { |
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.
const handleComplete = async (todoId: number, | |
event: React.ChangeEvent<HTMLInputElement>) => { | |
const handleComplete = async ( | |
todoId: number, | |
event: React.ChangeEvent<HTMLInputElement> | |
) => { |
Fix formatting in your code in all places
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
const BASE_URL = 'https://mate.academy/students-api'; | ||
|
||
// returns a promise resolved after a given delay |
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.
Remove comments
DEMO LINK