From 549a879789be19175808bbc5592226b82b1caff8 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov Date: Wed, 11 Oct 2023 11:24:52 +0300 Subject: [PATCH 1/4] movies list add form v1 --- README.md | 4 +- src/App.tsx | 15 +++++- src/components/NewMovie/NewMovie.tsx | 67 ++++++++++++++++++++++---- src/components/TextField/TextField.tsx | 1 + 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 13c9a6c74..17fe23f4c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ You have the `App` with the `MoviesList` and `NewMovie` form containing ready to use `TextField` components. Learn how it works and implement an ability to add movies from [IMDB](https://www.imdb.com/). -If you want to test your page you can get first image from a [movie page](https://www.imdb.com/title/tt1312171) using `DevTools` -> `Network` -> `Img` +If you want to test your page you can get first image from a [movie page](https://www.imdb.com/title/tt1312171) using `DevTools` -> `Network` -> `Img` > Here is [the demo page](https://mate-academy.github.io/react_movies-list-add-form/) @@ -30,4 +30,4 @@ const pattern = /^((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|( - 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 `` with your Github username in the [DEMO LINK](https://.github.io/react_movies-list-add-form/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://oltarasov.github.io/react_movies-list-add-form/) and add it to the PR description. diff --git a/src/App.tsx b/src/App.tsx index 34be670b0..7d7ca6ecb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,27 @@ import './App.scss'; +import { useState } from 'react'; import { MoviesList } from './components/MoviesList'; import { NewMovie } from './components/NewMovie'; import moviesFromServer from './api/movies.json'; export const App = () => { + const [movies, setMovies] = useState([...moviesFromServer]); + return (
- +
- {}} */ /> + { + setMovies(prevState => [...prevState, movie]); + }} + />
); diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 34f22fb0a..5f460a67d 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -1,45 +1,91 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; import { TextField } from '../TextField'; +import { Movie } from '../../types/Movie'; -export const NewMovie = () => { +type Props = { + onAdd: (movie: Movie) => void +}; + +export const NewMovie: React.FC = ({ onAdd }) => { // Increase the count after successful form submission // to reset touched status of all the `Field`s - const [count] = useState(0); + + const [count, setCount] = useState(0); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [imgUrl, setImgUrl] = useState(''); + const [imdbUrl, setImdbUrl] = useState(''); + const [imdbId, setImdbId] = useState(''); + + const readyToSubmit = [title, imgUrl, imdbUrl, imdbId] + .every(item => item !== ''); + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + setCount(prevState => prevState + 1); + + onAdd({ + title: title.trim(), + description: description.trim(), + imdbId: imdbId.trim(), + imdbUrl: imdbUrl.trim(), + imgUrl: imgUrl.trim(), + }); + + // [setTitle, setDescription, setImgUrl, setImdbUrl, setImdbId] + // .forEach(func => func('')); + }; return ( -
+

Add a movie

{}} + value={title} + onChange={(value) => { + setTitle(value); + }} required /> { + setDescription(value); + }} /> { + setImgUrl(value); + }} + required /> { + setImdbUrl(value); + }} + required /> { + setImdbId(value); + }} />
@@ -48,6 +94,7 @@ export const NewMovie = () => { type="submit" data-cy="submit-button" className="button is-link" + disabled={!readyToSubmit} > Add diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index 307b19865..518723c47 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -1,5 +1,6 @@ import classNames from 'classnames'; import React, { useState } from 'react'; +// import { Movie } from '../../types/Movie'; type Props = { name: string, From 98dd9e1681506e5f444e55b34c89f9db71495eb1 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov Date: Wed, 11 Oct 2023 16:32:18 +0300 Subject: [PATCH 2/4] movies list add form v1.2 --- src/App.tsx | 22 +++++++++++++--------- src/components/NewMovie/NewMovie.tsx | 6 +++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 7d7ca6ecb..c937a077b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,24 +4,28 @@ import { MoviesList } from './components/MoviesList'; import { NewMovie } from './components/NewMovie'; import moviesFromServer from './api/movies.json'; +type Movie = { + title: string; + description: string; + imgUrl: string; + imdbUrl: string; + imdbId: string; +}; + export const App = () => { const [movies, setMovies] = useState([...moviesFromServer]); + const addNewMovie = (movie: Movie) => { + setMovies(prevState => [...prevState, movie]); + }; + return (
- { - setMovies(prevState => [...prevState, movie]); - }} - /> +
); diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 5f460a67d..9b1882537 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -32,8 +32,8 @@ export const NewMovie: React.FC = ({ onAdd }) => { imgUrl: imgUrl.trim(), }); - // [setTitle, setDescription, setImgUrl, setImdbUrl, setImdbId] - // .forEach(func => func('')); + [setTitle, setDescription, setImgUrl, setImdbUrl, setImdbId] + .forEach(func => func('')); }; return ( @@ -43,7 +43,7 @@ export const NewMovie: React.FC = ({ onAdd }) => { { setTitle(value); }} From 4033b1fd1d31813016dcb8225c22dbcb929c1568 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov Date: Wed, 11 Oct 2023 19:13:38 +0300 Subject: [PATCH 3/4] movies list add form v1.3 --- src/components/NewMovie/NewMovie.tsx | 72 ++++++++++++++------------ src/components/TextField/TextField.tsx | 3 -- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 9b1882537..28cdb33f3 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -11,29 +11,43 @@ export const NewMovie: React.FC = ({ onAdd }) => { // to reset touched status of all the `Field`s const [count, setCount] = useState(0); - const [title, setTitle] = useState(''); - const [description, setDescription] = useState(''); - const [imgUrl, setImgUrl] = useState(''); - const [imdbUrl, setImdbUrl] = useState(''); - const [imdbId, setImdbId] = useState(''); - const readyToSubmit = [title, imgUrl, imdbUrl, imdbId] - .every(item => item !== ''); + const [movieDef, setMovieDef] = useState({ + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', + }); + + const readyToSubmit = Object.values(movieDef).every((item) => item !== ''); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); setCount(prevState => prevState + 1); onAdd({ - title: title.trim(), - description: description.trim(), - imdbId: imdbId.trim(), - imdbUrl: imdbUrl.trim(), - imgUrl: imgUrl.trim(), + title: movieDef.title.trim(), + description: movieDef.description.trim(), + imdbId: movieDef.imdbId.trim(), + imdbUrl: movieDef.imdbUrl.trim(), + imgUrl: movieDef.imgUrl.trim(), + }); + + setMovieDef({ + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', }); + }; - [setTitle, setDescription, setImgUrl, setImdbUrl, setImdbId] - .forEach(func => func('')); + const handleFieldChange = (name: string, value: string) => { + setMovieDef((prevData) => ({ + ...prevData, + [name]: value, + })); }; return ( @@ -43,49 +57,39 @@ export const NewMovie: React.FC = ({ onAdd }) => { { - setTitle(value); - }} + value={movieDef.title} + onChange={(value) => handleFieldChange('title', value)} required /> { - setDescription(value); - }} + value={movieDef.description} + onChange={(value) => handleFieldChange('description', value)} /> { - setImgUrl(value); - }} + value={movieDef.imgUrl} + onChange={(value) => handleFieldChange('imgUrl', value)} required /> { - setImdbUrl(value); - }} + value={movieDef.imdbUrl} + onChange={(value) => handleFieldChange('imdbUrl', value)} required /> { - setImdbId(value); - }} + value={movieDef.imdbId} + onChange={(value) => handleFieldChange('imdbId', value)} />
diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index 518723c47..3bf56f17a 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -1,6 +1,5 @@ import classNames from 'classnames'; import React, { useState } from 'react'; -// import { Movie } from '../../types/Movie'; type Props = { name: string, @@ -25,10 +24,8 @@ export const TextField: React.FC = ({ required = false, onChange = () => {}, }) => { - // generage a unique id once on component load const [id] = useState(() => `${name}-${getRandomDigits()}`); - // To show errors only if the field was touched (onBlur) const [touched, setTouched] = useState(false); const hasError = touched && required && !value; From 5105a8e94cab7242a370309fc50c585648c32556 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov Date: Wed, 11 Oct 2023 19:52:48 +0300 Subject: [PATCH 4/4] movies list add form v1.4 --- src/components/NewMovie/NewMovie.tsx | 30 +++++++++++++------------- src/components/TextField/TextField.tsx | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 28cdb33f3..a695740cb 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -6,21 +6,27 @@ type Props = { onAdd: (movie: Movie) => void }; +const initialMovieState = { + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', +}; + export const NewMovie: React.FC = ({ onAdd }) => { // Increase the count after successful form submission // to reset touched status of all the `Field`s const [count, setCount] = useState(0); - const [movieDef, setMovieDef] = useState({ - title: '', - description: '', - imgUrl: '', - imdbUrl: '', - imdbId: '', - }); + const [movieDef, setMovieDef] = useState(initialMovieState); - const readyToSubmit = Object.values(movieDef).every((item) => item !== ''); + const readyToSubmit = Object.entries(movieDef).every( + ([key, value]) => key === 'description' + || key === 'imdbId' + || (value.trim() !== '' && value !== undefined), + ); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); @@ -34,13 +40,7 @@ export const NewMovie: React.FC = ({ onAdd }) => { imgUrl: movieDef.imgUrl.trim(), }); - setMovieDef({ - title: '', - description: '', - imgUrl: '', - imdbUrl: '', - imdbId: '', - }); + setMovieDef(initialMovieState); }; const handleFieldChange = (name: string, value: string) => { diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index 3bf56f17a..bd40cecf3 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -27,7 +27,7 @@ export const TextField: React.FC = ({ const [id] = useState(() => `${name}-${getRandomDigits()}`); const [touched, setTouched] = useState(false); - const hasError = touched && required && !value; + const hasError = touched && required && !value.trim(); return (