├── 01-pure-to-jsx
├── _start
│ └── index.js
└── finish
│ └── index.js
├── 02-state-clicker
├── _start
│ └── App.jsx
└── finish
│ └── App.jsx
├── 03-lifecicle-timer
├── _start
│ └── App.jsx
└── finish
│ └── App.jsx
├── 04-fn-flow
├── _start
│ ├── App.jsx
│ ├── Post.jsx
│ └── Posts.jsx
└── finish
│ ├── App.jsx
│ ├── Post.jsx
│ └── Posts.jsx
├── 05-form-subscription
├── _start
│ └── SubscriptionForm.jsx
└── finish
│ └── SubscriptionForm.jsx
├── 06-movies-list
├── _start
│ ├── Main.jsx
│ ├── Movie.jsx
│ └── Movies.jsx
└── finish
│ ├── Main.jsx
│ ├── Movie.jsx
│ └── Movies.jsx
├── 07-movies-search
├── _start
│ ├── Main.jsx
│ └── Search.jsx
└── finish
│ ├── Main.jsx
│ └── Search.jsx
├── 08-movies-filter
├── _start
│ ├── Main.jsx
│ └── Search.jsx
└── finish
│ ├── Main.jsx
│ └── Search.jsx
├── 09-clicker-fn
├── _start
│ └── App.jsx
└── finish
│ └── App.jsx
├── 10-timer-fn
├── _start
│ └── App.js
└── finish
│ └── App.js
├── 11-movies-refactor
├── _start
│ └── Main.jsx
└── finish
│ └── Main.jsx
├── 12-shop-add-item
├── _start
│ └── Shop.jsx
└── finish
│ └── Shop.jsx
├── 13-shop-change-quantity
├── _start
│ └── Shop.jsx
└── finish
│ └── Shop.jsx
├── 14-reducer-practice
├── _start
│ ├── context.js
│ └── reducer.js
└── finish
│ ├── context.js
│ └── reducer.js
└── README.md
/01-pure-to-jsx/_start/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | // tip: чтобы встроить переменную в JSX используйте фигурные скобки {name}
5 | const Book = (props) => {
6 | return React.createElement("div", {}, [
7 | React.createElement("h2", {}, props.name),
8 | React.createElement("p", {}, props.year),
9 | React.createElement("p", {}, props.price)
10 | ]);
11 | };
12 |
13 | const App = () => {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
22 | // React.createElement("div", {}, [
23 | // React.createElement(
24 | // "h1",
25 | // { id: "hello", className: "class1" },
26 | // "Hello from React"
27 | // ),
28 | // React.createElement(Book, {
29 | // name: "JS for beginners",
30 | // year: 2018,
31 | // price: 1000
32 | // }),
33 | // React.createElement(Book, { name: "React", year: 2020, price: 1200 }),
34 | // React.createElement(Book, { name: "Vue JS", year: 2019, price: 1100 })
35 | // ]);
36 | };
37 |
38 |
39 | const rootElement = document.getElementById("root");
40 | ReactDOM.render(
41 |
42 |
43 | ,
44 |
45 | rootElement
46 | );
47 |
--------------------------------------------------------------------------------
/01-pure-to-jsx/finish/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 |
5 | const Book = (props) => {
6 | return
7 |
{props.name}
8 |
{props.year}
9 |
{props.price}
10 |
11 |
12 | // React.createElement("div", {}, [
13 | // React.createElement("h2", {}, props.name),
14 | // React.createElement("p", {}, props.year),
15 | // React.createElement("p", {}, props.price)
16 | // ]);
17 | };
18 |
19 | const App = () => {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 |
30 | const rootElement = document.getElementById("root");
31 | ReactDOM.render(
32 |
33 |
34 | ,
35 |
36 | rootElement
37 | );
38 |
--------------------------------------------------------------------------------
/02-state-clicker/_start/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class App extends Component {
4 | state = {
5 | count: 0,
6 | };
7 |
8 | handleClick = () => {
9 | this.setState({ count: this.state.count + 1 });
10 | };
11 |
12 | render() {
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/02-state-clicker/finish/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class App extends Component {
4 | state = {
5 | count: 0,
6 | };
7 |
8 | increment = () => {
9 | this.setState({ count: this.state.count + 1 });
10 | };
11 |
12 | decrement = () => {
13 | this.setState({ count: this.state.count - 1 });
14 | };
15 |
16 | render() {
17 | return (
18 |
19 |
20 | {this.state.count}
21 |
22 |
23 | );
24 | }
25 | }
26 |
27 | export default App;
28 |
--------------------------------------------------------------------------------
/03-lifecicle-timer/_start/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './styles.css';
3 |
4 | export default class App extends React.Component {
5 | state = {
6 | count: 0,
7 | isCounting: false,
8 | };
9 |
10 | componentDidMount() {}
11 |
12 | componentDidUpdate() {}
13 |
14 | componentWillUnmount() {}
15 |
16 | render() {
17 | return (
18 |
19 |
React Timer
20 | {this.state.count}
21 | {!this.state.isCounting ? (
22 |
23 | ) : (
24 |
25 | )}
26 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/03-lifecicle-timer/finish/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './styles.css';
3 |
4 | export default class App extends React.Component {
5 | state = {
6 | count: 0,
7 | isCounting: false,
8 | };
9 |
10 | componentDidMount() {
11 | const userCount = localStorage.getItem('count');
12 | if (userCount) this.setState({ count: +userCount });
13 | }
14 |
15 | componentDidUpdate() {
16 | localStorage.setItem('count', this.state.count);
17 | }
18 |
19 | handleReset = () => {
20 | this.setState({
21 | count: 0,
22 | isCounting: false,
23 | });
24 | };
25 |
26 | handleStart = () => {
27 | this.setState({
28 | isCounting: true,
29 | });
30 | this.counterId = setInterval(() => {
31 | this.setState({ count: this.state.count + 1 });
32 | }, 1000);
33 | };
34 |
35 | handleStop = () => {
36 | clearInterval(this.counterId);
37 | this.setState({
38 | isCounting: false,
39 | });
40 | };
41 |
42 | componentWillUnmount() {
43 | clearInterval(this.counterId);
44 | this.handleStop();
45 | }
46 |
47 | render() {
48 | return (
49 |
50 |
React Timer
51 | {this.state.count}
52 | {!this.state.isCounting ? (
53 |
54 | ) : (
55 |
56 | )}
57 |
58 |
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/04-fn-flow/_start/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Posts } from './components/Posts';
3 |
4 | class App extends Component {
5 | state = {
6 | posts: [
7 | { id: 'abc1', name: 'JS Basics' },
8 | { id: 'abc2', name: 'JS Advanced' },
9 | { id: 'abc3', name: 'Recat JS' },
10 | ],
11 | };
12 |
13 | render() {
14 | const { posts } = this.state;
15 |
16 | return (
17 |
20 | );
21 | }
22 | }
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/04-fn-flow/_start/Post.jsx:
--------------------------------------------------------------------------------
1 | export function Post(props) {
2 | const { name } = props;
3 |
4 | return (
5 |
6 | {name}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/04-fn-flow/_start/Posts.jsx:
--------------------------------------------------------------------------------
1 | import { Post } from './Post';
2 |
3 | export function Posts(props) {
4 | return (
5 |
6 | {props.posts.map((post) => (
7 |
8 | ))}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/04-fn-flow/finish/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Posts } from './Posts';
3 |
4 | class App extends Component {
5 | state = {
6 | posts: [
7 | { id: 'abc1', name: 'JS Basics' },
8 | { id: 'abc2', name: 'JS Advanced' },
9 | { id: 'abc3', name: 'Recat JS' },
10 | ],
11 | };
12 |
13 | removePost = (id) => {
14 | this.setState({
15 | posts: this.state.posts.filter((post) => post.id !== id),
16 | });
17 | };
18 |
19 | render() {
20 | const { posts } = this.state;
21 |
22 | return (
23 |
26 | );
27 | }
28 | }
29 |
30 | export default App;
31 |
--------------------------------------------------------------------------------
/04-fn-flow/finish/Post.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function Post(props) {
4 | const { id, name, removePost } = props;
5 |
6 | return (
7 |
8 | {name}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/04-fn-flow/finish/Posts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Post } from './Post';
3 |
4 | export function Posts(props) {
5 | return (
6 |
7 | {props.posts.map((post) => (
8 |
14 | ))}
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/05-form-subscription/_start/SubscriptionForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class SubscriptionForm extends React.Component {
4 | state = {
5 | email: '',
6 | isAgreeWithTerms: false,
7 | };
8 |
9 | render() {
10 | const { email, isAgreeWithTerms } = this.state;
11 |
12 | return (
13 |
14 |
20 |
21 |
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | export { SubscriptionForm };
37 |
--------------------------------------------------------------------------------
/05-form-subscription/finish/SubscriptionForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class SubscriptionForm extends React.Component {
4 | state = {
5 | email: '',
6 | isAgreeWithTerms: false,
7 | };
8 |
9 | handleEmail = (event) => {
10 | this.setState({ email: event.target.value });
11 | };
12 |
13 | handleCheckbox = (event) => {
14 | this.setState({
15 | isAgreeWithTerms: event.target.checked,
16 | });
17 | };
18 |
19 | handleSubmit = () => {
20 | const isValidEmail = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
21 | this.state.email.toLocaleLowerCase()
22 | );
23 | const isValidCheckbox = this.state.isAgreeWithTerms;
24 |
25 | if (!isValidEmail) {
26 | alert('Your email is not valid');
27 | return;
28 | }
29 |
30 | if (!isValidCheckbox) {
31 | alert('You should accept all terms and conditions');
32 | return;
33 | }
34 |
35 | this.setState({
36 | email: '',
37 | isAgreeWithTerms: false,
38 | });
39 | alert('Thank you for subscription!');
40 | };
41 |
42 | render() {
43 | const { email, isAgreeWithTerms } = this.state;
44 |
45 | return (
46 |
47 |
54 |
55 |
64 |
65 |
66 |
67 | );
68 | }
69 | }
70 |
71 | export { SubscriptionForm };
72 |
--------------------------------------------------------------------------------
/06-movies-list/_start/Main.jsx:
--------------------------------------------------------------------------------
1 | function Main() {
2 | return ;
3 | }
4 |
5 | export { Main };
6 |
--------------------------------------------------------------------------------
/06-movies-list/_start/Movie.jsx:
--------------------------------------------------------------------------------
1 | function Movie() {
2 | return ;
3 | }
4 |
5 | export { Movie };
6 |
--------------------------------------------------------------------------------
/06-movies-list/_start/Movies.jsx:
--------------------------------------------------------------------------------
1 | function Movies() {
2 | return ;
3 | }
4 |
5 | export { Movies };
6 |
--------------------------------------------------------------------------------
/06-movies-list/finish/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Movies } from '../components/Movies';
3 |
4 | class Main extends React.Component {
5 | state = {
6 | movies: [],
7 | };
8 |
9 | componentDidMount() {
10 | fetch('http://www.omdbapi.com/?apikey=78584b3c&s=matrix')
11 | .then((response) => response.json())
12 | .then((data) => this.setState({ movies: data.Search }));
13 | }
14 |
15 | render() {
16 | const { movies } = this.state;
17 |
18 | return (
19 |
20 | {movies.length ? (
21 |
22 | ) : (
23 | Loading...
24 | )}
25 |
26 | );
27 | }
28 | }
29 |
30 | export { Main };
31 |
--------------------------------------------------------------------------------
/06-movies-list/finish/Movie.jsx:
--------------------------------------------------------------------------------
1 | function Movie(props) {
2 | const {
3 | Title: title,
4 | Year: year,
5 | imdbID: id,
6 | Type: type,
7 | Poster: poster,
8 | } = props;
9 |
10 | console.log('movie render');
11 |
12 | return (
13 |
14 |
15 | {poster === 'N/A' ? (
16 |

20 | ) : (
21 |

22 | )}
23 |
24 |
25 |
26 | {title}
27 |
28 |
29 | {year} {type}
30 |
31 |
32 |
33 | );
34 | }
35 | export { Movie };
36 |
--------------------------------------------------------------------------------
/06-movies-list/finish/Movies.jsx:
--------------------------------------------------------------------------------
1 | import { Movie } from './Movie';
2 |
3 | function Movies(props) {
4 | const { movies } = props;
5 |
6 | return (
7 |
8 | {movies.map((movie) => (
9 |
10 | ))}
11 |
12 | );
13 | }
14 | export { Movies };
15 |
--------------------------------------------------------------------------------
/07-movies-search/_start/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Movies } from '../components/Movies';
3 | import { Search } from '../components/Search';
4 | import { Preloader } from '../components/Preloader';
5 |
6 | class Main extends React.Component {
7 | state = {
8 | movies: [],
9 | };
10 |
11 | componentDidMount() {
12 | fetch('http://www.omdbapi.com/?apikey=78584b3c&s=matrix')
13 | .then((response) => response.json())
14 | .then((data) => this.setState({ movies: data.Search }));
15 | }
16 |
17 | render() {
18 | const { movies } = this.state;
19 |
20 | return (
21 |
22 |
23 | {movies.length ? (
24 |
25 | ) : (
26 |
27 | )}
28 |
29 | );
30 | }
31 | }
32 |
33 | export { Main };
34 |
--------------------------------------------------------------------------------
/07-movies-search/_start/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Search extends React.Component {
4 | state = {
5 | search: '',
6 | };
7 |
8 | render() {
9 | return (
10 |
11 |
12 |
18 | this.setState({ search: e.target.value })
19 | }
20 | />
21 |
22 |
23 | );
24 | }
25 | }
26 |
27 | export { Search };
28 |
--------------------------------------------------------------------------------
/07-movies-search/finish/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Movies } from '../components/Movies';
3 | import { Search } from '../components/Search';
4 | import { Preloader } from '../components/Preloader';
5 |
6 | class Main extends React.Component {
7 | state = {
8 | movies: [],
9 | };
10 |
11 | searchMovies = (str) => {
12 | fetch(`http://www.omdbapi.com/?apikey=78584b3c&s=${str}`)
13 | .then((response) => response.json())
14 | .then((data) => this.setState({ movies: data.Search }));
15 | };
16 |
17 | componentDidMount() {
18 | fetch('http://www.omdbapi.com/?apikey=78584b3c&s=matrix')
19 | .then((response) => response.json())
20 | .then((data) => this.setState({ movies: data.Search }));
21 | }
22 |
23 | render() {
24 | const { movies } = this.state;
25 |
26 | return (
27 |
28 |
29 | {movies.length ? (
30 |
31 | ) : (
32 |
33 | )}
34 |
35 | );
36 | }
37 | }
38 |
39 | export { Main };
40 |
--------------------------------------------------------------------------------
/07-movies-search/finish/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Search extends React.Component {
4 | state = {
5 | search: '',
6 | };
7 |
8 | handleKey = (event) => {
9 | if (event.key === 'Enter') {
10 | this.props.searchMovies(this.state.search);
11 | }
12 | };
13 |
14 | render() {
15 | return (
16 |
17 |
18 |
24 | this.setState({ search: e.target.value })
25 | }
26 | onKeyDown={this.handleKey}
27 | />
28 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export { Search };
43 |
--------------------------------------------------------------------------------
/08-movies-filter/_start/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Movies } from '../components/Movies';
3 | import { Search } from '../components/Search';
4 | import { Preloader } from '../components/Preloader';
5 |
6 | class Main extends React.Component {
7 | state = {
8 | movies: [],
9 | };
10 |
11 | componentDidMount() {
12 | fetch('http://www.omdbapi.com/?apikey=78584b3c&s=matrix')
13 | .then((response) => response.json())
14 | .then((data) => this.setState({ movies: data.Search }));
15 | }
16 |
17 | searchMovies = (str) => {
18 | fetch(`http://www.omdbapi.com/?apikey=78584b3c&s=${str}`)
19 | .then((response) => response.json())
20 | .then((data) => this.setState({ movies: data.Search }));
21 | };
22 |
23 | render() {
24 | const { movies } = this.state;
25 |
26 | return (
27 |
28 |
29 | {movies.length ? (
30 |
31 | ) : (
32 |
33 | )}
34 |
35 | );
36 | }
37 | }
38 |
39 | export { Main };
40 |
--------------------------------------------------------------------------------
/08-movies-filter/_start/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Search extends React.Component {
4 | state = {
5 | search: '',
6 | };
7 |
8 | render() {
9 | return (
10 |
11 |
12 |
18 | this.setState({ search: e.target.value })
19 | }
20 | />
21 |
22 |
23 | );
24 | }
25 | }
26 |
27 | export { Search };
28 |
--------------------------------------------------------------------------------
/08-movies-filter/finish/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Movies } from '../components/Movies';
3 | import { Search } from '../components/Search';
4 | import { Preloader } from '../components/Preloader';
5 |
6 | class Main extends React.Component {
7 | state = {
8 | movies: [],
9 | };
10 |
11 | componentDidMount() {
12 | fetch('http://www.omdbapi.com/?apikey=78584b3c&s=matrix')
13 | .then((response) => response.json())
14 | .then((data) => this.setState({ movies: data.Search }));
15 | }
16 |
17 | searchMovies = (str, type = 'all') => {
18 | fetch(
19 | `http://www.omdbapi.com/?apikey=78584b3c&s=${str}${
20 | type !== 'all' ? `&type=${type}` : ''
21 | }`
22 | )
23 | .then((response) => response.json())
24 | .then((data) => this.setState({ movies: data.Search }));
25 | };
26 |
27 | render() {
28 | const { movies } = this.state;
29 |
30 | return (
31 |
32 |
33 | {!movies.length ? : }
34 |
35 | );
36 | }
37 | }
38 |
39 | export { Main };
40 |
--------------------------------------------------------------------------------
/08-movies-filter/finish/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Search extends React.Component {
4 | state = {
5 | search: '',
6 | type: 'all',
7 | };
8 |
9 | handleKey = (event) => {
10 | if (event.key === 'Enter') {
11 | this.props.searchMovies(this.state.search, this.state.type);
12 | }
13 | };
14 |
15 | handleFilter = (event) => {
16 | this.setState(
17 | () => ({ type: event.target.dataset.type }),
18 | () => {
19 | this.props.searchMovies(this.state.search, this.state.type);
20 | }
21 | );
22 | };
23 |
24 | render() {
25 | return (
26 |
86 | );
87 | }
88 | }
89 |
90 | export { Search };
91 |
--------------------------------------------------------------------------------
/09-clicker-fn/_start/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class App extends Component {
4 | state = {
5 | count: 0,
6 | };
7 |
8 | increment = () => {
9 | this.setState({ count: this.state.count + 1 });
10 | };
11 |
12 | decrement = () => {
13 | this.setState({ count: this.state.count - 1 });
14 | };
15 |
16 | render() {
17 | return (
18 |
19 |
20 | {this.state.count}
21 |
22 |
23 | );
24 | }
25 | }
26 |
27 | export default App;
28 |
--------------------------------------------------------------------------------
/09-clicker-fn/finish/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | function App() {
4 | const [count, setCount] = useState(0);
5 |
6 | const increment = () => {
7 | setCount(count + 1);
8 | };
9 |
10 | const decrement = () => {
11 | setCount(count - 1);
12 | };
13 |
14 | return (
15 |
16 |
17 |
18 | {count}
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/10-timer-fn/_start/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class App extends React.Component {
4 | state = {
5 | count: 0,
6 | isCounting: false,
7 | };
8 |
9 | componentDidMount() {
10 | const userCount = localStorage.getItem('count');
11 | if (userCount) this.setState({ count: +userCount });
12 | }
13 |
14 | componentDidUpdate() {
15 | localStorage.setItem('count', this.state.count);
16 | }
17 |
18 | handleReset = () => {
19 | this.setState({
20 | count: 0,
21 | isCounting: false,
22 | });
23 | };
24 |
25 | handleStart = () => {
26 | this.setState({
27 | isCounting: true,
28 | });
29 | this.counterId = setInterval(() => {
30 | this.setState({ count: this.state.count + 1 });
31 | }, 1000);
32 | };
33 |
34 | handleStop = () => {
35 | clearInterval(this.counterId);
36 | this.setState({
37 | isCounting: false,
38 | });
39 | };
40 |
41 | componentWillUnmount() {
42 | clearInterval(this.counterId);
43 | this.handleStop();
44 | }
45 |
46 | render() {
47 | return (
48 |
49 |
React Timer
50 | {this.state.count}
51 | {!this.state.isCounting ? (
52 |
53 | ) : (
54 |
55 | )}
56 |
57 |
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/10-timer-fn/finish/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 |
3 | function setDefaultValue() {
4 | const userCount = localStorage.getItem('count');
5 | return userCount ? +userCount : 0;
6 | }
7 |
8 | export default function Timer() {
9 | const [count, setCount] = useState(setDefaultValue());
10 | const [isCounting, setIsCount] = useState(false);
11 | const timerIdRef = useRef(null);
12 |
13 | const handleReset = () => {
14 | setCount(0);
15 | setIsCount(false);
16 | };
17 |
18 | const handleStart = () => {
19 | setIsCount(true);
20 | };
21 |
22 | const handleStop = () => {
23 | setIsCount(false);
24 | };
25 |
26 | useEffect(() => {
27 | localStorage.setItem('count', count);
28 | }, [count]);
29 |
30 | useEffect(() => {
31 | if (isCounting) {
32 | timerIdRef.current = setInterval(() => {
33 | setCount((prevCount) => prevCount + 1);
34 | }, 1000);
35 | }
36 |
37 | return () => {
38 | timerIdRef.current && clearInterval(timerIdRef.current);
39 | timerIdRef.current = null;
40 | };
41 | }, [isCounting]);
42 |
43 | return (
44 |
45 |
React Timer
46 | {count}
47 | {!isCounting ? (
48 |
49 | ) : (
50 |
51 | )}
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/11-movies-refactor/_start/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Movies } from '../components/Movies';
3 | import { Search } from '../components/Search';
4 | import { Preloader } from '../components/Preloader';
5 |
6 | const API_KEY = process.env.REACT_APP_API_KEY;
7 |
8 | class Main extends React.Component {
9 | state = {
10 | movies: [],
11 | loading: true,
12 | };
13 |
14 | componentDidMount() {
15 | console.log(process.env);
16 | fetch(`https://www.omdbapi.com/?apikey=${API_KEY}&s=matrix`)
17 | .then((response) => response.json())
18 | .then((data) =>
19 | this.setState({ movies: data.Search, loading: false })
20 | )
21 | .catch((err) => {
22 | console.error(err);
23 | this.setState({ loading: false });
24 | });
25 | }
26 |
27 | searchMovies = (str, type = 'all') => {
28 | this.setState({ loading: true });
29 | fetch(
30 | `https://www.omdbapi.com/?apikey=${API_KEY}&s=${str}${
31 | type !== 'all' ? `&type=${type}` : ''
32 | }`
33 | )
34 | .then((response) => response.json())
35 | .then((data) =>
36 | this.setState({ movies: data.Search, loading: false })
37 | )
38 | .catch((err) => {
39 | console.error(err);
40 | this.setState({ loading: false });
41 | });
42 | };
43 |
44 | render() {
45 | const { movies, loading } = this.state;
46 |
47 | return (
48 |
49 |
50 | {loading ? : }
51 |
52 | );
53 | }
54 | }
55 |
56 | export { Main };
57 |
--------------------------------------------------------------------------------
/11-movies-refactor/finish/Main.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michey85/react-course-practice/355488a228b0d36e80f8857368662117491ebca6/11-movies-refactor/finish/Main.jsx
--------------------------------------------------------------------------------
/12-shop-add-item/_start/Shop.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { API_KEY, API_URL } from '../config';
3 |
4 | import { Preloader } from './Preloader';
5 | import { GoodsList } from './GoodsList';
6 | import { Cart } from './Cart';
7 |
8 | function Shop() {
9 | const [goods, setGoods] = useState([]);
10 | const [loading, setLoading] = useState(true);
11 | const [order, setOrder] = useState([]);
12 |
13 | const addToBasket = (item) => {};
14 |
15 | useEffect(function getGoods() {
16 | fetch(API_URL, {
17 | headers: {
18 | Authorization: API_KEY,
19 | },
20 | })
21 | .then((response) => response.json())
22 | .then((data) => {
23 | data.featured && setGoods(data.featured);
24 | setLoading(false);
25 | });
26 | }, []);
27 |
28 | return (
29 |
30 |
31 | {loading ? (
32 |
33 | ) : (
34 |
35 | )}
36 |
37 | );
38 | }
39 |
40 | export { Shop };
41 |
--------------------------------------------------------------------------------
/12-shop-add-item/finish/Shop.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { API_KEY, API_URL } from '../config';
3 |
4 | import { Preloader } from './Preloader';
5 | import { GoodsList } from './GoodsList';
6 | import { Cart } from './Cart';
7 |
8 | function Shop() {
9 | const [goods, setGoods] = useState([]);
10 | const [loading, setLoading] = useState(true);
11 | const [order, setOrder] = useState([]);
12 |
13 | const addToBasket = (item) => {
14 | const itemIndex = order.findIndex(
15 | (orderItem) => orderItem.id === item.id
16 | );
17 |
18 | if (itemIndex < 0) {
19 | const newItem = {
20 | ...item,
21 | quantity: 1,
22 | };
23 | setOrder([...order, newItem]);
24 | } else {
25 | const newOrder = order.map((orderItem, index) => {
26 | if (index === itemIndex) {
27 | return {
28 | ...orderItem,
29 | quantity: orderItem.quantity + 1,
30 | };
31 | } else {
32 | return orderItem;
33 | }
34 | });
35 |
36 | setOrder(newOrder);
37 | }
38 | };
39 |
40 | useEffect(function getGoods() {
41 | fetch(API_URL, {
42 | headers: {
43 | Authorization: API_KEY,
44 | },
45 | })
46 | .then((response) => response.json())
47 | .then((data) => {
48 | data.featured && setGoods(data.featured);
49 | setLoading(false);
50 | });
51 | }, []);
52 |
53 | return (
54 |
55 |
56 | {loading ? (
57 |
58 | ) : (
59 |
60 | )}
61 |
62 | );
63 | }
64 |
65 | export { Shop };
66 |
--------------------------------------------------------------------------------
/13-shop-change-quantity/_start/Shop.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { API_KEY, API_URL } from '../config';
3 |
4 | import { Preloader } from './Preloader';
5 | import { GoodsList } from './GoodsList';
6 | import { Cart } from './Cart';
7 | import { BasketList } from './BasketList';
8 |
9 | function Shop() {
10 | const [goods, setGoods] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 | const [order, setOrder] = useState([]);
13 | const [isBasketShow, setBasketSow] = useState(false);
14 |
15 | const addToBasket = (item) => {
16 | const itemIndex = order.findIndex(
17 | (orderItem) => orderItem.id === item.id
18 | );
19 |
20 | if (itemIndex < 0) {
21 | const newItem = {
22 | ...item,
23 | quantity: 1,
24 | };
25 | setOrder([...order, newItem]);
26 | } else {
27 | const newOrder = order.map((orderItem, index) => {
28 | if (index === itemIndex) {
29 | return {
30 | ...orderItem,
31 | quantity: orderItem.quantity + 1,
32 | };
33 | } else {
34 | return orderItem;
35 | }
36 | });
37 |
38 | setOrder(newOrder);
39 | }
40 | setAlertName(item.name);
41 | };
42 |
43 | const removeFromBasket = (itemId) => {
44 | const newOrder = order.filter((el) => el.id !== itemId);
45 | setOrder(newOrder);
46 | };
47 |
48 | const incQuantity = (itemId) => {};
49 | const decQuantity = (itemId) => {};
50 |
51 | const handleBasketShow = () => {
52 | setBasketSow(!isBasketShow);
53 | };
54 |
55 | useEffect(function getGoods() {
56 | fetch(API_URL, {
57 | headers: {
58 | Authorization: API_KEY,
59 | },
60 | })
61 | .then((response) => response.json())
62 | .then((data) => {
63 | data.featured && setGoods(data.featured);
64 | setLoading(false);
65 | });
66 | }, []);
67 |
68 | return (
69 |
70 |
71 | {loading ? (
72 |
73 | ) : (
74 |
75 | )}
76 | {isBasketShow && (
77 |
82 | )}
83 |
84 | );
85 | }
86 |
87 | export { Shop };
88 |
--------------------------------------------------------------------------------
/13-shop-change-quantity/finish/Shop.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { API_KEY, API_URL } from '../config';
3 |
4 | import { Preloader } from './Preloader';
5 | import { GoodsList } from './GoodsList';
6 | import { Cart } from './Cart';
7 | import { BasketList } from './BasketList';
8 |
9 | function Shop() {
10 | const [goods, setGoods] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 | const [order, setOrder] = useState([]);
13 | const [isBasketShow, setBasketSow] = useState(false);
14 |
15 | const addToBasket = (item) => {
16 | const itemIndex = order.findIndex(
17 | (orderItem) => orderItem.id === item.id
18 | );
19 |
20 | if (itemIndex < 0) {
21 | const newItem = {
22 | ...item,
23 | quantity: 1,
24 | };
25 | setOrder([...order, newItem]);
26 | } else {
27 | const newOrder = order.map((orderItem, index) => {
28 | if (index === itemIndex) {
29 | return {
30 | ...orderItem,
31 | quantity: orderItem.quantity + 1,
32 | };
33 | } else {
34 | return orderItem;
35 | }
36 | });
37 |
38 | setOrder(newOrder);
39 | }
40 | setAlertName(item.name);
41 | };
42 |
43 | const removeFromBasket = (itemId) => {
44 | const newOrder = order.filter((el) => el.id !== itemId);
45 | setOrder(newOrder);
46 | };
47 |
48 | const incQuantity = (itemId) => {
49 | const newOrder = order.map((el) => {
50 | if (el.id === itemId) {
51 | const newQuantity = el.quantity + 1;
52 | return {
53 | ...el,
54 | quantity: newQuantity,
55 | };
56 | } else {
57 | return el;
58 | }
59 | });
60 | setOrder(newOrder);
61 | };
62 | const decQuantity = (itemId) => {
63 | const newOrder = order.map((el) => {
64 | if (el.id === itemId) {
65 | const newQuantity = el.quantity - 1;
66 | return {
67 | ...el,
68 | quantity: newQuantity >= 0 ? newQuantity : 0,
69 | };
70 | } else {
71 | return el;
72 | }
73 | });
74 | setOrder(newOrder);
75 | };
76 |
77 | const handleBasketShow = () => {
78 | setBasketSow(!isBasketShow);
79 | };
80 |
81 | useEffect(function getGoods() {
82 | fetch(API_URL, {
83 | headers: {
84 | Authorization: API_KEY,
85 | },
86 | })
87 | .then((response) => response.json())
88 | .then((data) => {
89 | data.featured && setGoods(data.featured);
90 | setLoading(false);
91 | });
92 | }, []);
93 |
94 | return (
95 |
96 |
97 | {loading ? (
98 |
99 | ) : (
100 |
101 | )}
102 | {isBasketShow && (
103 |
110 | )}
111 |
112 | );
113 | }
114 |
115 | export { Shop };
116 |
--------------------------------------------------------------------------------
/14-reducer-practice/_start/context.js:
--------------------------------------------------------------------------------
1 | import { createContext, useReducer } from 'react';
2 | import { reducer } from './reducer';
3 |
4 | export const ShopContext = createContext();
5 |
6 | const initialState = {
7 | goods: [],
8 | loading: true,
9 | order: [],
10 | isBasketShow: false,
11 | alertName: '',
12 | };
13 |
14 | export const ContextProvider = ({ children }) => {
15 | const [value, dispatch] = useReducer(reducer, initialState);
16 |
17 | value.closeAlert = () => {
18 | dispatch({ type: 'CLOSE_ALERT' });
19 | };
20 |
21 | value.removeFromBasket = (itemId) => {
22 | dispatch({ type: 'REMOVE_FROM_BASKET', payload: { id: itemId } });
23 | };
24 |
25 | return (
26 | {children}
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/14-reducer-practice/_start/reducer.js:
--------------------------------------------------------------------------------
1 | export function reducer(state, { type, payload }) {
2 | switch (type) {
3 | case 'REMOVE_FROM_BASKET':
4 | return {
5 | ...state,
6 | order: state.order.filter((el) => el.id !== payload.id),
7 | };
8 | case 'CLOSE_ALERT':
9 | return {
10 | ...state,
11 | alertName: '',
12 | };
13 | default:
14 | return state;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/14-reducer-practice/finish/context.js:
--------------------------------------------------------------------------------
1 | import { createContext, useReducer } from 'react';
2 | import { reducer } from './reducer';
3 |
4 | export const ShopContext = createContext();
5 |
6 | const initialState = {
7 | goods: [],
8 | loading: true,
9 | order: [],
10 | isBasketShow: false,
11 | alertName: '',
12 | };
13 |
14 | export const ContextProvider = ({ children }) => {
15 | const [value, dispatch] = useReducer(reducer, initialState);
16 |
17 | value.closeAlert = () => {
18 | dispatch({ type: 'CLOSE_ALERT' });
19 | };
20 |
21 | value.addToBasket = (item) => {
22 | dispatch({ type: 'ADD_TO_BASKET', payload: item });
23 | };
24 |
25 | value.incQuantity = (itemId) => {
26 | dispatch({ type: 'INCREMENT_QUANTITY', payload: { id: itemId } });
27 | };
28 |
29 | value.decQuantity = (itemId) => {
30 | dispatch({ type: 'DECREMENT_QUANTITY', payload: { id: itemId } });
31 | };
32 |
33 | value.removeFromBasket = (itemId) => {
34 | dispatch({ type: 'REMOVE_FROM_BASKET', payload: { id: itemId } });
35 | };
36 |
37 | value.handleBasketShow = () => {
38 | dispatch({ type: 'TOGGLE_BASKET' });
39 | };
40 |
41 | return (
42 | {children}
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/14-reducer-practice/finish/reducer.js:
--------------------------------------------------------------------------------
1 | export function reducer(state, { type, payload }) {
2 | switch (type) {
3 | case 'ADD_TO_BASKET': {
4 | const itemIndex = state.order.findIndex(
5 | (orderItem) => orderItem.id === payload.id
6 | );
7 |
8 | let newOrder = null;
9 | if (itemIndex < 0) {
10 | const newItem = {
11 | ...payload,
12 | quantity: 1,
13 | };
14 | newOrder = [...state.order, newItem];
15 | } else {
16 | newOrder = state.order.map((orderItem, index) => {
17 | if (index === itemIndex) {
18 | return {
19 | ...orderItem,
20 | quantity: orderItem.quantity + 1,
21 | };
22 | } else {
23 | return orderItem;
24 | }
25 | });
26 | }
27 |
28 | return {
29 | ...state,
30 | order: newOrder,
31 | alertName: payload.name,
32 | };
33 | }
34 | case 'REMOVE_FROM_BASKET':
35 | return {
36 | ...state,
37 | order: state.order.filter((el) => el.id !== payload.id),
38 | };
39 | case 'INCREMENT_QUANTITY':
40 | return {
41 | ...state,
42 | order: state.order.map((el) => {
43 | if (el.id === payload.id) {
44 | const newQuantity = el.quantity + 1;
45 | return {
46 | ...el,
47 | quantity: newQuantity,
48 | };
49 | } else {
50 | return el;
51 | }
52 | }),
53 | };
54 | case 'DECREMENT_QUANTITY':
55 | return {
56 | ...state,
57 | order: state.order.map((el) => {
58 | if (el.id === payload.id) {
59 | const newQuantity = el.quantity - 1;
60 | return {
61 | ...el,
62 | quantity: newQuantity >= 0 ? newQuantity : 0,
63 | };
64 | } else {
65 | return el;
66 | }
67 | }),
68 | };
69 | case 'CLOSE_ALERT':
70 | return {
71 | ...state,
72 | alertName: '',
73 | };
74 | case 'TOGGLE_BASKET':
75 | return {
76 | ...state,
77 | isBasketShow: !state.isBasketShow,
78 | };
79 | default:
80 | return state;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Практические задания к курсу "React с нуля для начинающих"
2 |
3 | Практические задания разбиты по директориям с соответствующей нумерацией.
4 |
5 | Внутри каждой практики есть две папки:
6 | - ***_start*** - содержит необходимые файлы для начала выполнения практического задания
7 | - ***finish*** - содержит один из возможных вариантов решения практического задания
8 |
--------------------------------------------------------------------------------