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

add solution #2913

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
# React Add TODO Form

Implement the ability to add TODOs to the `TodoList` implemented in the **Static List of TODOs**

> Here is [the working example](https://mate-academy.github.io/react_add-todo-form/)

1. Create an `App` component storing the `todos` array and displaying it using the `TodoList`.
1. Create a form to add new TODOs:
- there should be a text input for the `title` with `data-cy="titleInput"` attribute;
- add a `<select>` with `data-cy="userSelect"` attribute showing all the given users;
- add labels and placeholders where they are needed;
- add a new todo to the list after clicking the `Add` button;
- each TODO should have `id`, `title`, `userId`, and `completed` (`false` by default);
- `id` is the largest `id` in the array + 1 (add `data-id={todo.id}` attribute to each `.TodoInfo`).
- there should be a text input for the `title` with `data-cy="titleInput"` attribute;
- add a `<select>` with `data-cy="userSelect"` attribute showing all the given users;
- add labels and placeholders where they are needed;
- add a new todo to the list after clicking the `Add` button;
- each TODO should have `id`, `title`, `userId`, and `completed` (`false` by default);
- `id` is the largest `id` in the array + 1 (add `data-id={todo.id}` attribute to each `.TodoInfo`).
1. Add validation to the form:
- add a default empty option `Choose a user` to the select;
- before creating a todo, check if a `user` was selected; if not, show an error message next to the `select` (`Please choose a user`);
- if the `title` is empty, show an error message next to the `title` field (`Please enter a title`);
- errors should appear only after clicking the `Add` button;
- hide the message immediately after any change of the field with an error;
- add a default empty option `Choose a user` to the select;
- before creating a todo, check if a `user` was selected; if not, show an error message next to the `select` (`Please choose a user`);
- if the `title` is empty, show an error message next to the `title` field (`Please enter a title`);
- errors should appear only after clicking the `Add` button;
- hide the message immediately after any change of the field with an error;
1. If the form is valid, add a todo to the list and clear the form.
1. (* **Optional**) Allow entering only letters (`ua` and `en`), digits, and `spaces` in the `title` field. Just remove any other characters from the `title`.
1. (\* **Optional**) Allow entering only letters (`ua` and `en`), digits, and `spaces` in the `title` field. Just remove any other characters from the `title`.

## Instructions

- Install Prettier Extention and use this [VSCode settings](https://mate-academy.github.io/fe-program/tools/vscode/settings.json) to enable format on save.
- 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).
- Open one more terminal and run tests with `npm test` to ensure your solution is correct.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://<your_account>.github.io/react_add-todo-form/) and add it to the PR description.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://clavigo.github.io/react_add-todo-form/) and add it to the PR description.
60 changes: 11 additions & 49 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,23 @@
import React from 'react';
import './App.scss';

// import usersFromServer from './api/users';
// import todosFromServer from './api/todos';
import usersFromServer from './api/users';
import todosFromServer from './api/todos';
import { Todo } from './types/Todo';
import { TodoList } from './components/TodoList';
import { Form } from './components/Form';

export const App = () => {
const [todos, setTodos] = React.useState<Todo[]>(todosFromServer);
const [users] = React.useState(usersFromServer);

return (
<div className="App">
<h1>Add todo form</h1>

<form action="/api/todos" method="POST">
<div className="field">
<input type="text" data-cy="titleInput" />
<span className="error">Please enter a title</span>
</div>

<div className="field">
<select data-cy="userSelect">
<option value="0" disabled>
Choose a user
</option>
</select>

<span className="error">Please choose a user</span>
</div>

<button type="submit" data-cy="submitButton">
Add
</button>
</form>

<section className="TodoList">
<article data-id="1" className="TodoInfo TodoInfo--completed">
<h2 className="TodoInfo__title">delectus aut autem</h2>

<a className="UserInfo" href="mailto:Sincere@april.biz">
Leanne Graham
</a>
</article>

<article data-id="15" className="TodoInfo TodoInfo--completed">
<h2 className="TodoInfo__title">delectus aut autem</h2>

<a className="UserInfo" href="mailto:Sincere@april.biz">
Leanne Graham
</a>
</article>

<article data-id="2" className="TodoInfo">
<h2 className="TodoInfo__title">
quis ut nam facilis et officia qui
</h2>
<Form todos={todos} setTodos={setTodos} users={users} />

<a className="UserInfo" href="mailto:Julianne.OConner@kory.org">
Patricia Lebsack
</a>
</article>
</section>
<TodoList todos={todos} users={users} />
</div>
);
};
104 changes: 104 additions & 0 deletions src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { Todo } from '../../types/Todo';
import { User } from '../../types/User';

type FormProps = {
todos: Todo[];
setTodos: React.Dispatch<React.SetStateAction<Todo[]>>;
users: User[];
};

export const Form: React.FC<FormProps> = ({ todos, setTodos, users }) => {
const [title, setTitle] = React.useState('');
const [titleError, setTitleError] = React.useState(false);

const [select, setSelect] = React.useState(0);

Choose a reason for hiding this comment

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

Consider initializing select with null or undefined instead of 0 to better represent an unselected state, as 0 might be a valid user ID.

const [selectError, setSelectError] = React.useState(false);

const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
setTitleError(false);
};

const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelect(+e.target.value);
setSelectError(false);
};

const currentTodos = (newTodo: Todo) => {
setTodos([...todos, newTodo]);
};

const getNewId = (todosArray: Todo[]) => {
const maxId = Math.max(...todosArray.map(todo => todo.id));

return maxId + 1;
};

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

setTitleError(!title);
setSelectError(!select);
Comment on lines +41 to +42

Choose a reason for hiding this comment

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

The error state for titleError and selectError is set based on the current values of title and select. Ensure that the error messages are correctly displayed when the form is submitted with invalid inputs.


if (!title || !select) {
return;
}
Comment on lines +44 to +46

Choose a reason for hiding this comment

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

The condition if (!title || !select) checks for empty title or unselected user. Ensure that select is initialized to a value that accurately represents an unselected state, as mentioned earlier.


currentTodos({
id: getNewId(todos),
title,
completed: false,
userId: select,
});

setTitle('');
setSelect(0);
};

return (
<form action="/api/todos" method="POST" onSubmit={handleSubmit}>
<div className="field">
<label htmlFor="title">Title: </label>
<input
id="title"
type="text"
data-cy="titleInput"
placeholder="Enter a title"
value={title}
onChange={handleTitleChange}
/>
<span className="error">
{titleError ? 'Please enter a title' : ''}
</span>
</div>

<div className="field">
<label htmlFor="users">User: </label>
<select
id="users"
data-cy="userSelect"
value={select}
onChange={handleSelectChange}
>
<option value="0" disabled>
Choose a user
</option>
{users.map(user => (
<option key={user.id} value={user.id}>
{user.name}
</option>
))}
</select>

<span className="error">
{selectError ? 'Please choose a user' : ''}
</span>
</div>

<button type="submit" data-cy="submitButton">
Add
</button>
</form>
);
};
1 change: 1 addition & 0 deletions src/components/Form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Form';
26 changes: 25 additions & 1 deletion src/components/TodoInfo/TodoInfo.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
export const TodoInfo = () => {};
import React from 'react';
import cn from 'classnames';
import { UserInfo } from '../UserInfo';
import { User } from '../../types/User';

Choose a reason for hiding this comment

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

According to the checklist, you should not use the import keyword in non-root components. Instead, prepare data in the App component and pass it to child components. Consider passing the necessary data for UserInfo from the App component to TodoInfo and then to UserInfo to adhere to this guideline.


type TodoInfoProps = {
id: number;
title: string;
completed: boolean;
userId: number;
user: User;
};

export const TodoInfo: React.FC<TodoInfoProps> = todo => {
return (
<article
data-id={todo.id}
className={cn('TodoInfo', { 'TodoInfo--completed': todo.completed })}
>
<h2 className="TodoInfo__title">{todo.title}</h2>

<UserInfo user={todo.user} />
</article>
);
};
32 changes: 31 additions & 1 deletion src/components/TodoList/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
export const TodoList = () => {};
import React from 'react';
import { TodoInfo } from '../TodoInfo';
import { Todos } from '../../types/Todos';
import { User } from '../../types/User';

Choose a reason for hiding this comment

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

Ensure that the Todos type correctly represents the props being passed to the TodoList component. It should include both todos and users arrays.


export const TodoList: React.FC<Todos> = ({ todos, users }) => {
return (
<section className="TodoList">
{todos.map(todo => {
const user = users.find(
(findedUser: User) => findedUser.id === todo.userId,
);

if (!user) {
return;
}

Choose a reason for hiding this comment

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

Returning undefined from the map function when no user is found can lead to issues. Consider returning null instead to avoid rendering issues.


return (
<TodoInfo
key={todo.id}
id={todo.id}
title={todo.title}
completed={todo.completed}
userId={todo.userId}
user={user}
/>
);
})}
</section>
);
};
19 changes: 18 additions & 1 deletion src/components/UserInfo/UserInfo.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
export const UserInfo = () => {};
import React from 'react';
import { User } from '../../types/User';

type UserInfoProps = {
user: User;
};

export const UserInfo: React.FC<UserInfoProps> = ({ user }) => {
return (
<>
{user && (
<a className="UserInfo" href={`mailto:${user.email}`}>
{user.name}
</a>
)}
</>
);
};
6 changes: 6 additions & 0 deletions src/types/Todo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type Todo = {
id: number;
title: string;
completed: boolean;
userId: number;
};
7 changes: 7 additions & 0 deletions src/types/Todos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Todo } from './Todo';
import { User } from './User';

export type Todos = {
todos: Todo[];
users: User[];
};
6 changes: 6 additions & 0 deletions src/types/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type User = {
id: number;
name: string;
username: string;
email: string;
};
Loading