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

Create todo app using API #767

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

Conversation

davinnchii
Copy link

Copy link

@polosanya polosanya left a comment

Choose a reason for hiding this comment

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

Nice start!
It is impossible to delete todos on editing
delete-todos
image

src/App.tsx Outdated
Comment on lines 95 to 97
<NewTodo
onWaiting={setTempTodo}
/>

Choose a reason for hiding this comment

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

Suggested change
<NewTodo
onWaiting={setTempTodo}
/>
<NewTodo onWaiting={setTempTodo} />

Better write it in one line if there are < 3 props

const { setErrorMessage, setIsErrorHidden } = useErrorMessage();
const [newTitle, setNewTitle] = useState('');

const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {

Choose a reason for hiding this comment

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

Suggested change
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {

https://javascript.plainenglish.io/handy-naming-conventions-for-event-handler-functions-props-in-react-fc1cbb791364

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.

#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:
image

src/App.tsx Outdated
Comment on lines 47 to 49
.catch((error) => {
setErrorMessage(JSON.parse(error.message).error);
setIsErrorHidden(false);

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)

Comment on lines 30 to 32
href={key === 'All'
? '#/'
: `#/${Filters[key][0].toLowerCase() + Filters[key].slice(1)}`}

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>)

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.

Copy link

@DenisChakhalian DenisChakhalian left a 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!

video

  • 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)

  • editing is not canceled after pressing Escape
    image

  • image

  • image
    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

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.

Great work 👍
Some functionality doesn't work properly, also left comments with suggestions, check them

src/App.tsx Outdated
Comment on lines 41 to 52
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));
});
};

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

Comment on lines +54 to +60
useEffect(() => {
getTodos(USER_ID)
.then(setTodos)
.catch(() => {
handleShowError('Unable to load todos');
});
}, []);

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

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
Comment on lines 98 to 99
<div className={'modal-background'
+ ' has-background-white-ter'}

Choose a reason for hiding this comment

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

Suggested change
<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?

Copy link
Author

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)}

Choose a reason for hiding this comment

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

Suggested change
onFilterChange={(newFilter) => setFilterParam(newFilter)}
onFilterChange={setFilterParam}


export const ErrorMessageContextProvider: React.FC<Props> = ({ children }) => {
const [errorMessage, setErrorMessage] = useState('');
const [isErrorHidden, setIsErrorHidden] = useState(true);

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

Copy link
Author

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

Comment on lines 187 to 188
'is-active': loadingTodos.find(({ todoId }) => (
todoId === todo.id))?.isLoading,

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`;

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)}`;

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

}) => {
return (
<>
{todos?.map(todo => (

Choose a reason for hiding this comment

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

Suggested change
{todos?.map(todo => (
{todos.map(todo => (

todos is required, you don't need to check

Comment on lines 1 to 3
export interface UpdateCompleted {
completed: boolean,
}

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

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.

Would be good to see some error message why I can't enter empty value and save it
Also, now I can create todo with just spaces entered

image

The same with renaming, I can rename todo to empty(when I enter just spaces)

src/App.tsx Outdated
Comment on lines 72 to 76
case Filters.All:
return todos;

default:
return todos;

Choose a reason for hiding this comment

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

Suggested change
case Filters.All:
return todos;
default:
return todos;
case Filters.All:
default:
return todos;

src/App.tsx Outdated
Comment on lines 25 to 39
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 }) => (

Choose a reason for hiding this comment

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

Better to keep all states together, all your own hooks also together, all constants together and all useEffects also together

<section className="todoapp__main">
<TodoList todos={visibleTodos} />

{tempTodo && (

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"
/>

Choose a reason for hiding this comment

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

Suggested change

Comment on lines 32 to 37
onWaiting({
id: 0,
completed: false,
title: newTitle,
userId: USER_ID,
});

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

Comment on lines 59 to 60
const changingTodos = countTodos(todos, false).length
? countTodos(todos, false)

Choose a reason for hiding this comment

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

Not fixed

Comment on lines 65 to 66
setLoadingTodos(prev => prev.filter(id => (
id !== todo.id)));

Choose a reason for hiding this comment

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

Suggested change
setLoadingTodos(prev => prev.filter(id => (
id !== todo.id)));
setLoadingTodos(prev => (
prev.filter(id => (id !== todo.id)
));

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

Comment on lines 164 to 165
'is-active': loadingTodos.some((id) => (
id === todo.id)),

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}

Choose a reason for hiding this comment

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

Create constant for this

Copy link
Author

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.

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.

🥇

setLoadingTodos(prev => prev.filter(id => id !== todo.id));
};

const onDelete = async (todoId: number) => {
Copy link

@Moroz-Dmytro Moroz-Dmytro Sep 22, 2023

Choose a reason for hiding this comment

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

Suggested change
const onDelete = async (todoId: number) => {
const handleDeleteTodo = async (todoId: number) => {

Comment on lines +102 to +115
<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>

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

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.

Looks great 👍
Left a few suggestions to improve your solution.

Comment on lines +54 to +60
useEffect(() => {
getTodos(USER_ID)
.then(setTodos)
.catch(() => {
handleShowError('Unable to load todos');
});
}, []);

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

Comment on lines +67 to +69
changingTodos.forEach(({ id }) => {
setLoadingTodos(prev => [...prev, id]);
});

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

Comment on lines +72 to +82
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)));
});

Choose a reason for hiding this comment

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

Here as well

Comment on lines +77 to +78
const handleComplete = async (todoId: number,
event: React.ChangeEvent<HTMLInputElement>) => {

Choose a reason for hiding this comment

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

Suggested change
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

Choose a reason for hiding this comment

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

Remove comments

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.

6 participants