-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
base: master
Are you sure you want to change the base?
add solution #2913
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
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> | ||
); | ||
}; |
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); | ||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error state for |
||
|
||
if (!title || !select) { | ||
return; | ||
} | ||
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The condition |
||
|
||
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> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Form'; |
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the checklist, you should not use the |
||
|
||
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> | ||
); | ||
}; |
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that the |
||
|
||
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; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning |
||
|
||
return ( | ||
<TodoInfo | ||
key={todo.id} | ||
id={todo.id} | ||
title={todo.title} | ||
completed={todo.completed} | ||
userId={todo.userId} | ||
user={user} | ||
/> | ||
); | ||
})} | ||
</section> | ||
); | ||
}; |
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> | ||
)} | ||
</> | ||
); | ||
}; |
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; | ||
}; |
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[]; | ||
}; |
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; | ||
}; |
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.
Consider initializing
select
withnull
orundefined
instead of0
to better represent an unselected state, as0
might be a valid user ID.