├── .gitignore
├── 01-New-Lifecycle-Methods
└── points
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ ├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── ErrorBoundary.js
│ ├── index.css
│ ├── index.js
│ ├── logo.png
│ ├── logo.svg
│ └── serviceWorker.js
│ └── yarn.lock
├── 02-Context-API
└── bank-app
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ ├── src
│ ├── App.test.js
│ ├── Root.js
│ ├── api
│ │ └── index.js
│ ├── components
│ │ ├── Charity.js
│ │ ├── FormatAmount.js
│ │ ├── Greeting.js
│ │ ├── TotalAmount.js
│ │ ├── User.js
│ │ ├── ViewAccountBalance.js
│ │ └── WithdrawButton.js
│ ├── containers
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Login.css
│ │ └── Login.js
│ ├── context
│ │ └── UserContext.js
│ ├── images
│ │ └── girl.png
│ ├── index.css
│ ├── index.js
│ └── registerServiceWorker.js
│ └── yarn.lock
├── 05-The-Profiler
├── bank-app-fix
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.test.js
│ │ ├── Root.js
│ │ ├── api
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── Charity.js
│ │ │ ├── FormatAmount.js
│ │ │ ├── Greeting.js
│ │ │ ├── TotalAmount.js
│ │ │ ├── User.js
│ │ │ ├── ViewAccountBalance.js
│ │ │ └── WithdrawButton.js
│ │ ├── containers
│ │ │ ├── App.css
│ │ │ ├── App.js
│ │ │ ├── Login.css
│ │ │ └── Login.js
│ │ ├── context
│ │ │ └── UserContext.js
│ │ ├── images
│ │ │ └── girl.png
│ │ ├── index.css
│ │ ├── index.js
│ │ └── registerServiceWorker.js
│ └── yarn.lock
└── fake-medium
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ ├── src
│ ├── actions
│ │ ├── api.js
│ │ ├── index.js
│ │ └── types.js
│ ├── components
│ │ ├── Article.js
│ │ ├── Clap.css
│ │ └── Clap.js
│ ├── containers
│ │ └── App.js
│ ├── index.css
│ ├── index.js
│ ├── middleware
│ │ └── api.js
│ ├── reducers
│ │ └── index.js
│ ├── registerServiceWorker.js
│ └── store
│ │ └── index.js
│ └── yarn.lock
├── 06-Lazy-Load
└── bank-app
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ ├── src
│ ├── App.test.js
│ ├── Root.js
│ ├── api
│ │ └── index.js
│ ├── components
│ │ ├── Charity.js
│ │ ├── FormatAmount.js
│ │ ├── Greeting.js
│ │ ├── TotalAmount.js
│ │ ├── User.js
│ │ ├── ViewAccountBalance.js
│ │ └── WithdrawButton.js
│ ├── containers
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Login.css
│ │ └── Login.js
│ ├── context
│ │ └── UserContext.js
│ ├── images
│ │ └── girl.png
│ ├── index.css
│ ├── index.js
│ └── registerServiceWorker.js
│ └── yarn.lock
├── 08-Advanced-Hook-Patterns
├── compound-component
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── components
│ │ │ ├── Body.css
│ │ │ ├── Body.js
│ │ │ ├── Expandable.css
│ │ │ ├── Expandable.js
│ │ │ ├── Header.css
│ │ │ ├── Header.js
│ │ │ ├── Icon.css
│ │ │ └── Icon.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ └── serviceWorker.js
│ └── yarn.lock
├── control-props
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── components
│ │ │ ├── Body.css
│ │ │ ├── Body.js
│ │ │ ├── Expandable.css
│ │ │ ├── Expandable.js
│ │ │ ├── Header.css
│ │ │ ├── Header.js
│ │ │ ├── Icon.css
│ │ │ └── Icon.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ └── serviceWorker.js
│ └── yarn.lock
├── prop-collection
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── components
│ │ │ ├── Body.css
│ │ │ ├── Body.js
│ │ │ ├── Expandable.css
│ │ │ ├── Expandable.js
│ │ │ ├── Header.css
│ │ │ ├── Header.js
│ │ │ ├── Icon.css
│ │ │ └── Icon.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── serviceWorker.js
│ │ ├── useEffectAfterMount.js
│ │ └── useExpanded.js
│ └── yarn.lock
├── prop-getters
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── components
│ │ │ ├── Body.css
│ │ │ ├── Body.js
│ │ │ ├── Expandable.css
│ │ │ ├── Expandable.js
│ │ │ ├── Header.css
│ │ │ ├── Header.js
│ │ │ ├── Icon.css
│ │ │ └── Icon.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── serviceWorker.js
│ │ ├── useEffectAfterMount.js
│ │ └── useExpanded.js
│ └── yarn.lock
├── state-initializers
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── components
│ │ │ ├── Body.css
│ │ │ ├── Body.js
│ │ │ ├── Expandable.css
│ │ │ ├── Expandable.js
│ │ │ ├── Header.css
│ │ │ ├── Header.js
│ │ │ ├── Icon.css
│ │ │ ├── Icon.js
│ │ │ └── utils.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── serviceWorker.js
│ │ ├── useEffectAfterMount.js
│ │ └── useExpanded.js
│ └── yarn.lock
└── state-reducer
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ ├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── components
│ │ ├── Body.css
│ │ ├── Body.js
│ │ ├── Expandable.css
│ │ ├── Expandable.js
│ │ ├── Header.css
│ │ ├── Header.js
│ │ ├── Icon.css
│ │ ├── Icon.js
│ │ └── utils.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── serviceWorker.js
│ ├── useEffectAfterMount.js
│ └── useExpanded.js
│ └── yarn.lock
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | *node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "points",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.6.3",
7 | "react-dom": "^16.6.3",
8 | "react-scripts": "2.1.1"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": [
20 | ">0.2%",
21 | "not dead",
22 | "not ie <= 11",
23 | "not op_mini all"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/01-New-Lifecycle-Methods/points/public/favicon.ico
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
24 | React App
25 |
26 |
27 |
28 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | display: flex;
4 | }
5 |
6 | .App-logo {
7 | animation: App-logo-spin infinite 20s linear;
8 | height: 40vmin;
9 | }
10 |
11 | .App-header {
12 | border: 1px solid #282c34;
13 | flex: 1;
14 | min-height: 100vh;
15 | display: flex;
16 | flex-direction: column;
17 | align-items: center;
18 | justify-content: center;
19 | font-size: calc(10px + 2vmin);
20 | }
21 |
22 | .App-header img {
23 | border: 0
24 | }
25 |
26 | .App-link {
27 | color: #61dafb;
28 | }
29 |
30 | .App-chat {
31 | min-width: 400px;
32 | /* background: linear-gradient(
33 | -45deg,
34 | #183850 0,
35 | #183850 25%,
36 | #192c46 50%,
37 | #22254c 75%,
38 | #22254c 100%
39 | )
40 | no-repeat; */
41 | }
42 |
43 | /**
44 | chat
45 | **/
46 | .chat-thread {
47 | padding: 0 20px 0 20px;
48 | list-style: none;
49 | display: flex;
50 | flex-direction: column;
51 | overflow-y: scroll;
52 | max-height: calc(100vh - 50px);
53 | }
54 |
55 | .chat-bubble {
56 | position: relative;
57 | background-color: rgba(25, 147, 147, 0.2);
58 | padding: 16px 40px 16px 20px;
59 | margin: 0 0 20px 0;
60 | border-radius: 10px;
61 | }
62 |
63 | .chat-bubble:nth-child(n) {
64 | margin-right: auto;
65 | }
66 |
67 | .chat-btn {
68 | padding: 5px;
69 | margin: 0 0 0 10px;
70 | }
71 |
72 | @keyframes App-logo-spin {
73 | from {
74 | transform: rotate(0deg);
75 | }
76 | to {
77 | transform: rotate(360deg);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import logo from './logo.png'
3 | import './App.css'
4 |
5 | class Chats extends Component {
6 | render () {
7 | return (
8 |
9 | {this.props.chatList.map((chat, i) => {
10 | if (i === 4) {
11 | // Simulate an error
12 | throw new Error('I crashed!')
13 | }
14 | return (
15 |
16 | {chat}
17 |
18 | )
19 | })}
20 |
21 | )
22 | }
23 | }
24 |
25 | class App extends Component {
26 | chatThreadRef = React.createRef()
27 |
28 | // NB: unconditionally overriding state here is generally a bad idea.
29 | static getDerivedStateFromProps (props, state) {
30 | return {
31 | points: 1000
32 | }
33 | }
34 | state = {
35 | points: 10,
36 | chatList: ['Hey', 'Hello', 'Hi']
37 | }
38 |
39 | _handleAddChat = () => {
40 | this.setState(prevState => ({
41 | chatList: [...prevState.chatList, 'Hello!!!']
42 | }))
43 | }
44 |
45 | getSnapshotBeforeUpdate (prevProps, prevState) {
46 | if (this.state.chatList > prevState.chatList) {
47 | const chatThreadRef = this.chatThreadRef.current
48 | return chatThreadRef.scrollHeight - chatThreadRef.scrollTop
49 | }
50 | return null
51 | }
52 |
53 | componentDidUpdate (prevProps, prevState, snapshot) {
54 | if (snapshot !== null) {
55 | const chatThreadRef = this.chatThreadRef.current
56 | chatThreadRef.scrollTop = chatThreadRef.scrollHeight - snapshot
57 | }
58 | }
59 |
60 | render () {
61 | return (
62 |
63 |
67 |
68 |
69 | Football Chat{' '}
70 |
73 |
74 |
77 |
78 |
79 | )
80 | }
81 | }
82 |
83 | export default App
84 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | class ErrorBoundary extends Component {
3 | state = { hasError: false };
4 |
5 | static getDerivedStateFromError(error) {
6 | console.log(`Error log from getDerivedStateFromError: ${error}`);
7 | return { hasError: true };
8 | }
9 |
10 | componentDidCatch(error, info) {
11 | console.log(`Error log from componentDidCatch: ${error}`);
12 | console.log(info);
13 | }
14 |
15 | render() {
16 | if (this.state.hasError) {
17 | return Something went wrong.
;
18 | }
19 | return this.props.children;
20 | }
21 | }
22 |
23 | export default ErrorBoundary;
24 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import * as serviceWorker from "./serviceWorker";
6 | import ErrorBoundary from "./ErrorBoundary";
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById("root")
13 | );
14 |
15 | // If you want your app to work offline and load faster, you can change
16 | // unregister() to register() below. Note this comes with some pitfalls.
17 | // Learn more about service workers: http://bit.ly/CRA-PWA
18 | serviceWorker.unregister();
19 |
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/01-New-Lifecycle-Methods/points/src/logo.png
--------------------------------------------------------------------------------
/01-New-Lifecycle-Methods/points/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/02-Context-API/bank-app/README.md
--------------------------------------------------------------------------------
/02-Context-API/bank-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bank-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "format-number": "^3.0.0",
7 | "react": "^16.8.6",
8 | "react-dom": "^16.8.6",
9 | "react-scripts": "1.1.4"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test --env=jsdom",
15 | "eject": "react-scripts eject"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/02-Context-API/bank-app/public/favicon.ico
--------------------------------------------------------------------------------
/02-Context-API/bank-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import App from './containers/App'
3 | import Login from './containers/Login'
4 | import UserProvider, { UserConsumer } from './context/UserContext'
5 |
6 | const Root = () => (
7 |
8 |
9 | {({ user, handleLogin }) =>
10 | user ? :
11 | }
12 |
13 |
14 | )
15 | export default Root
16 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/api/index.js:
--------------------------------------------------------------------------------
1 | export const USER = {
2 | name: 'June',
3 | totalAmount: 2500701
4 | }
5 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/components/Charity.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Charity = () => {
4 | return Give away all your cash to charity
5 | }
6 |
7 | export default Charity
8 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/components/FormatAmount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import formatNumber from 'format-number'
3 |
4 | const FormatAmount = ({ totalAmount }) => {
5 | return {formatNumber({ prefix: '$' })(totalAmount)}
6 | }
7 |
8 | export default FormatAmount
9 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/components/Greeting.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { UserConsumer } from '../context/UserContext'
3 |
4 | const Greeting = () => {
5 | return (
6 |
7 | {({ user }) => Welcome, {user.name}!
}
8 |
9 | )
10 | }
11 |
12 | export default Greeting
13 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/components/TotalAmount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FormatAmount from './FormatAmount'
3 |
4 | const TotalAmount = ({ totalAmount }) => {
5 | return (
6 |
10 | )
11 | }
12 |
13 | export default TotalAmount
14 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/components/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Greeting from './Greeting'
3 |
4 | const User = ({ profilePic }) => {
5 | return (
6 |
7 |

8 |
9 |
10 | )
11 | }
12 |
13 | export default User
14 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/components/ViewAccountBalance.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import TotalAmount from './TotalAmount'
3 | import { UserConsumer } from '../context/UserContext'
4 |
5 | const ViewAccountBalance = ({ showBalance, displayBalance }) => {
6 | return (
7 |
8 | {!showBalance ? (
9 |
10 |
11 | Would you love to view your account balance?{' '}
12 |
13 |
16 |
17 | ) : (
18 |
19 | {({ user }) => }
20 |
21 | )}
22 |
23 | )
24 | }
25 |
26 | export default ViewAccountBalance
27 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/components/WithdrawButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import formatNumber from 'format-number'
3 | import { UserConsumer } from '../context/UserContext'
4 |
5 | const WithdrawButton = ({ amount }) => {
6 | return (
7 |
8 | {({ handleWithdrawal }) => (
9 |
16 | )}
17 |
18 | )
19 | }
20 |
21 | export default WithdrawButton
22 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/containers/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | min-height: 100%;
3 | text-align: center;
4 | background-image: linear-gradient(
5 | to bottom,
6 | rgba(255, 255, 255, 0) 50%,
7 | #fdf6f6 50%,
8 | #fdf6f6 100%
9 | );
10 | display: flex;
11 | flex-direction: column;
12 | justify-content: center;
13 | }
14 |
15 | .App__greeting {
16 | color: #fff;
17 | font-weight: bold;
18 | font-size: 2rem;
19 |
20 | }
21 | .App__userpic {
22 | border-radius: 50%;
23 | width: 100px;
24 | height: 100px;
25 | align-self: center;
26 | }
27 | .App__amount {
28 | position: relative;
29 | align-self: center;
30 | background: #fff;
31 | color: rgba(0, 0, 0, 0.47);
32 | font-size: 2.1rem;
33 | font-weight: bold;
34 | padding: 2.5rem 5rem 1.5rem;
35 | border-radius: 11px;
36 | margin: 1rem 0 5rem 0;
37 | box-shadow: 0 10px 5px 5px rgba(0, 0, 0, 0.02);
38 | animation-duration: 0.75s;
39 | animation-name: bounceIn;
40 | }
41 | .App__amount:after {
42 | content: "";
43 | display: block;
44 | position: absolute;
45 | z-index: -1;
46 | width: 90%;
47 | height: 100%;
48 | top: 10%;
49 | left: 5%;
50 | background: #fff;
51 | box-shadow: 0 5px 34px 23px rgba(0, 0, 0, 0.06);
52 | border-radius: 11px;
53 | }
54 | .App__amount--info {
55 | font-size: 0.6rem;
56 | }
57 |
58 | .App__button {
59 | outline: 0;
60 | background: #fff;
61 | color: #fe718f;
62 | font-size: 1rem;
63 | padding: 1rem;
64 | margin: 1rem;
65 | border: 0;
66 | box-shadow: none;
67 | font-weight: bold;
68 | cursor: pointer;
69 | }
70 |
71 | .App__giveaway {
72 | font-size: 0.7rem;
73 | color: #fe8183;
74 | cursor: pointer;
75 | }
76 |
77 | .App__showBalance {
78 | opacity: 0.65;
79 | }
80 |
81 | .App__showBalanceCTA {
82 | margin: 1rem 0;
83 | }
84 |
85 | @keyframes bounceIn {
86 | from,
87 | 20%,
88 | 40%,
89 | 60%,
90 | 80%,
91 | to {
92 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
93 | }
94 |
95 | 0% {
96 | opacity: 0;
97 | transform: scale3d(0.3, 0.3, 0.3);
98 | }
99 |
100 | 20% {
101 | transform: scale3d(1.1, 1.1, 1.1);
102 | }
103 |
104 | 40% {
105 | transform: scale3d(0.9, 0.9, 0.9);
106 | }
107 |
108 | 60% {
109 | opacity: 1;
110 | transform: scale3d(1.03, 1.03, 1.03);
111 | }
112 |
113 | 80% {
114 | transform: scale3d(0.97, 0.97, 0.97);
115 | }
116 |
117 | to {
118 | opacity: 1;
119 | transform: scale3d(1, 1, 1);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import User from '../components/User'
3 | import WithdrawButton from '../components/WithdrawButton'
4 | import ViewAccountBalance from '../components/ViewAccountBalance'
5 | import Charity from '../components/Charity'
6 | import photographer from '../images/girl.png'
7 |
8 | import './App.css'
9 |
10 | class App extends Component {
11 | state = {
12 | showBalance: false
13 | }
14 |
15 | displayBalance = () => {
16 | this.setState({ showBalance: true })
17 | }
18 | render () {
19 | const { showBalance } = this.state
20 |
21 | return (
22 |
23 |
24 |
28 |
29 |
33 |
34 |
35 |
36 | )
37 | }
38 | }
39 |
40 | export default App
41 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/containers/Login.css:
--------------------------------------------------------------------------------
1 | .Login {
2 | min-height: 100vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | flex-direction: column
7 | }
8 |
9 | .Login label {
10 | display: block;
11 | margin: 0.6rem 0;
12 | }
13 |
14 | .Login input {
15 | padding: 0.5rem;
16 | margin-bottom: 0.6rem;
17 | min-height: 40px;
18 | font-size: 16px;
19 | min-width: 300px;
20 | border: 0;
21 | border-radius: 4px;
22 | }
23 | .Login button[type='submit'] {
24 | width: 300px;
25 | outline: 0;
26 | background: #fff;
27 | color: #fe718f;
28 | font-size: 1rem;
29 | padding: 1rem;
30 | margin: 1.5rem;
31 | border: 0;
32 | box-shadow: none;
33 | font-weight: bold;
34 | cursor: pointer;
35 | }
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/containers/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import './Login.css'
4 |
5 | class Login extends Component {
6 | state = {}
7 | handleFormInput = evt => {
8 | const { name, value } = evt.target
9 | this.setState({
10 | [name]: [value]
11 | })
12 | }
13 |
14 | render () {
15 | const { username = '', password = '' } = this.state
16 | const { handleLogin } = this.props
17 | return (
18 |
40 | )
41 | }
42 | }
43 |
44 | export default Login
45 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/context/UserContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, Component } from 'react'
2 | import { USER } from '../api'
3 |
4 | const { Provider, Consumer } = createContext()
5 |
6 | class UserProvider extends Component {
7 | state = {
8 | loggedInUser: null
9 | }
10 |
11 | handleLogin = evt => {
12 | evt.preventDefault()
13 | this.setState({
14 | loggedInUser: USER
15 | })
16 | }
17 |
18 | handleWithdrawal = evt => {
19 | const { name, totalAmount } = this.state.loggedInUser
20 | const withdrawalAmount = evt.target.dataset.amount
21 |
22 | this.setState({
23 | loggedInUser: {
24 | name,
25 | totalAmount: totalAmount - withdrawalAmount
26 | }
27 | })
28 | }
29 |
30 | render () {
31 | const { loggedInUser } = this.state
32 | return (
33 |
40 | {this.props.children}
41 |
42 | )
43 | }
44 | }
45 |
46 | export { UserProvider as default, Consumer as UserConsumer }
47 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/images/girl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/02-Context-API/bank-app/src/images/girl.png
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | width: 100%;
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 | *,
9 | *:before,
10 | *:after {
11 | box-sizing: border-box;
12 | }
13 |
14 | body {
15 | font-family: sans-serif;
16 | font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, “Roboto”, “Oxygen”,
17 | “Ubuntu”, “Cantarell”, “Fira Sans”, “Droid Sans”, “Helvetica Neue”,
18 | sans-serif;
19 | background-image: radial-gradient(#fe718f 0%, #fe8183 42%, #ff9b73 87%);
20 | }
21 |
22 | #root {
23 | height: 100%;
24 | }
25 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import Root from './Root'
5 | import registerServiceWorker from './registerServiceWorker'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 | registerServiceWorker()
9 |
--------------------------------------------------------------------------------
/02-Context-API/bank-app/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/05-The-Profiler/bank-app-fix/README.md
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bank-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "format-number": "^3.0.0",
7 | "react": "^16.8.6",
8 | "react-dom": "^16.8.6",
9 | "react-scripts": "1.1.4"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test --env=jsdom",
15 | "eject": "react-scripts eject"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/05-The-Profiler/bank-app-fix/public/favicon.ico
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import App from './containers/App'
3 | import Login from './containers/Login'
4 | import UserProvider, { UserConsumer } from './context/UserContext'
5 |
6 | const Root = () => (
7 |
8 |
9 | {({ user, handleLogin }) =>
10 | user ? :
11 | }
12 |
13 |
14 | )
15 | export default Root
16 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/api/index.js:
--------------------------------------------------------------------------------
1 | export const USER = {
2 | name: 'June',
3 | totalAmount: 2500701
4 | }
5 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/components/Charity.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 |
3 | const Charity = memo(() => {
4 | return Give away all your cash to charity
5 | })
6 |
7 | export default Charity
8 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/components/FormatAmount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import formatNumber from 'format-number'
3 |
4 | const FormatAmount = ({ totalAmount }) => {
5 | return {formatNumber({ prefix: '$' })(totalAmount)}
6 | }
7 |
8 | export default FormatAmount
9 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/components/Greeting.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { UserConsumer } from '../context/UserContext'
3 |
4 | const Greeting = () => {
5 | return (
6 |
7 | {({ user }) => Welcome, {user.name}!
}
8 |
9 | )
10 | }
11 |
12 | export default Greeting
13 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/components/TotalAmount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FormatAmount from './FormatAmount'
3 |
4 | const TotalAmount = ({ totalAmount }) => {
5 | return (
6 |
10 | )
11 | }
12 |
13 | export default TotalAmount
14 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/components/User.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import Greeting from './Greeting'
3 |
4 | const User = memo(({ profilePic }) => {
5 | return (
6 |
7 |

8 |
9 |
10 | )
11 | })
12 |
13 | User.displayName = 'User'
14 | export default User
15 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/components/ViewAccountBalance.js:
--------------------------------------------------------------------------------
1 | import React, { memo, Fragment } from 'react'
2 | import TotalAmount from './TotalAmount'
3 | import { UserConsumer } from '../context/UserContext'
4 |
5 | const ViewAccountBalance = memo(({ showBalance, displayBalance }) => {
6 | return (
7 |
8 | {!showBalance ? (
9 |
10 |
11 | Would you love to view your account balance?{' '}
12 |
13 |
16 |
17 | ) : (
18 |
19 | {({ user }) => }
20 |
21 | )}
22 |
23 | )
24 | })
25 |
26 | ViewAccountBalance.displayName = 'ViewAccountBalance'
27 | export default ViewAccountBalance
28 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/components/WithdrawButton.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import formatNumber from 'format-number'
3 | import { UserConsumer } from '../context/UserContext'
4 |
5 | const WithdrawButton = memo(({ amount }) => {
6 | return (
7 |
8 | {({ handleWithdrawal }) => (
9 |
16 | )}
17 |
18 | )
19 | })
20 |
21 | WithdrawButton.displayName = 'WithdrawButton'
22 | export default WithdrawButton
23 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/containers/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | min-height: 100%;
3 | text-align: center;
4 | background-image: linear-gradient(
5 | to bottom,
6 | rgba(255, 255, 255, 0) 50%,
7 | #fdf6f6 50%,
8 | #fdf6f6 100%
9 | );
10 | display: flex;
11 | flex-direction: column;
12 | justify-content: center;
13 | }
14 |
15 | .App__greeting {
16 | color: #fff;
17 | font-weight: bold;
18 | font-size: 2rem;
19 |
20 | }
21 | .App__userpic {
22 | border-radius: 50%;
23 | width: 100px;
24 | height: 100px;
25 | align-self: center;
26 | }
27 | .App__amount {
28 | position: relative;
29 | align-self: center;
30 | background: #fff;
31 | color: rgba(0, 0, 0, 0.47);
32 | font-size: 2.1rem;
33 | font-weight: bold;
34 | padding: 2.5rem 5rem 1.5rem;
35 | border-radius: 11px;
36 | margin: 1rem 0 5rem 0;
37 | box-shadow: 0 10px 5px 5px rgba(0, 0, 0, 0.02);
38 | animation-duration: 0.75s;
39 | animation-name: bounceIn;
40 | }
41 | .App__amount:after {
42 | content: "";
43 | display: block;
44 | position: absolute;
45 | z-index: -1;
46 | width: 90%;
47 | height: 100%;
48 | top: 10%;
49 | left: 5%;
50 | background: #fff;
51 | box-shadow: 0 5px 34px 23px rgba(0, 0, 0, 0.06);
52 | border-radius: 11px;
53 | }
54 | .App__amount--info {
55 | font-size: 0.6rem;
56 | }
57 |
58 | .App__button {
59 | outline: 0;
60 | background: #fff;
61 | color: #fe718f;
62 | font-size: 1rem;
63 | padding: 1rem;
64 | margin: 1rem;
65 | border: 0;
66 | box-shadow: none;
67 | font-weight: bold;
68 | cursor: pointer;
69 | }
70 |
71 | .App__giveaway {
72 | font-size: 0.7rem;
73 | color: #fe8183;
74 | cursor: pointer;
75 | }
76 |
77 | .App__showBalance {
78 | opacity: 0.65;
79 | }
80 |
81 | .App__showBalanceCTA {
82 | margin: 1rem 0;
83 | }
84 |
85 | @keyframes bounceIn {
86 | from,
87 | 20%,
88 | 40%,
89 | 60%,
90 | 80%,
91 | to {
92 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
93 | }
94 |
95 | 0% {
96 | opacity: 0;
97 | transform: scale3d(0.3, 0.3, 0.3);
98 | }
99 |
100 | 20% {
101 | transform: scale3d(1.1, 1.1, 1.1);
102 | }
103 |
104 | 40% {
105 | transform: scale3d(0.9, 0.9, 0.9);
106 | }
107 |
108 | 60% {
109 | opacity: 1;
110 | transform: scale3d(1.03, 1.03, 1.03);
111 | }
112 |
113 | 80% {
114 | transform: scale3d(0.97, 0.97, 0.97);
115 | }
116 |
117 | to {
118 | opacity: 1;
119 | transform: scale3d(1, 1, 1);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import User from '../components/User'
3 | import WithdrawButton from '../components/WithdrawButton'
4 | import ViewAccountBalance from '../components/ViewAccountBalance'
5 | import Charity from '../components/Charity'
6 | import photographer from '../images/girl.png'
7 |
8 | import './App.css'
9 |
10 | class App extends PureComponent {
11 | state = {
12 | showBalance: false
13 | }
14 |
15 | displayBalance = () => {
16 | this.setState({ showBalance: true })
17 | }
18 | render () {
19 | const { showBalance } = this.state
20 |
21 | return (
22 |
23 |
24 |
28 |
29 |
33 |
34 |
35 |
36 | )
37 | }
38 | }
39 |
40 | export default App
41 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/containers/Login.css:
--------------------------------------------------------------------------------
1 | .Login {
2 | min-height: 100vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | flex-direction: column
7 | }
8 |
9 | .Login label {
10 | display: block;
11 | margin: 0.6rem 0;
12 | }
13 |
14 | .Login input {
15 | padding: 0.5rem;
16 | margin-bottom: 0.6rem;
17 | min-height: 40px;
18 | font-size: 16px;
19 | min-width: 300px;
20 | border: 0;
21 | border-radius: 4px;
22 | }
23 | .Login button[type='submit'] {
24 | width: 300px;
25 | outline: 0;
26 | background: #fff;
27 | color: #fe718f;
28 | font-size: 1rem;
29 | padding: 1rem;
30 | margin: 1.5rem;
31 | border: 0;
32 | box-shadow: none;
33 | font-weight: bold;
34 | cursor: pointer;
35 | }
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/containers/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import './Login.css'
4 |
5 | class Login extends Component {
6 | state = {}
7 | handleFormInput = evt => {
8 | const { name, value } = evt.target
9 | this.setState({
10 | [name]: [value]
11 | })
12 | }
13 |
14 | render () {
15 | const { username = '', password = '' } = this.state
16 | const { handleLogin } = this.props
17 | return (
18 |
40 | )
41 | }
42 | }
43 |
44 | export default Login
45 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/context/UserContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, Component } from 'react'
2 | import { USER } from '../api'
3 |
4 | const { Provider, Consumer } = createContext()
5 |
6 | class UserProvider extends Component {
7 | constructor () {
8 | super()
9 | this.state = {
10 | user: null,
11 | handleLogin: this.handleLogin,
12 | handleWithdrawal: this.handleWithdrawal
13 | }
14 | }
15 |
16 | handleLogin = evt => {
17 | evt.preventDefault()
18 | this.setState({
19 | user: USER
20 | })
21 | }
22 |
23 | handleWithdrawal = evt => {
24 | const { name, totalAmount } = this.state.user
25 | const withdrawalAmount = evt.target.dataset.amount
26 |
27 | this.setState({
28 | user: {
29 | name,
30 | totalAmount: totalAmount - withdrawalAmount
31 | }
32 | })
33 | }
34 |
35 | render () {
36 | return {this.props.children}
37 | }
38 | }
39 |
40 | export { UserProvider as default, Consumer as UserConsumer }
41 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/images/girl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/05-The-Profiler/bank-app-fix/src/images/girl.png
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | width: 100%;
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 | *,
9 | *:before,
10 | *:after {
11 | box-sizing: border-box;
12 | }
13 |
14 | body {
15 | font-family: sans-serif;
16 | font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, “Roboto”, “Oxygen”,
17 | “Ubuntu”, “Cantarell”, “Fira Sans”, “Droid Sans”, “Helvetica Neue”,
18 | sans-serif;
19 | background-image: radial-gradient(#fe718f 0%, #fe8183 42%, #ff9b73 87%);
20 | }
21 |
22 | #root {
23 | height: 100%;
24 | }
25 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import Root from './Root'
5 | import registerServiceWorker from './registerServiceWorker'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 | registerServiceWorker()
9 |
--------------------------------------------------------------------------------
/05-The-Profiler/bank-app-fix/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ```js
4 | {
5 | "numberOfRecommends": 1900,
6 | "title": "My First Fake Medium Post",
7 | "subtitle": "and why it makes no intelligible sense",
8 | "paragraphs": [
9 | {
10 | "text": "This is supposed to be an intelligible post about something intelligible."
11 | },
12 | {
13 | "text": "Uh, sorry there’s nothing here."
14 | },
15 | {
16 | "text": "It’s just a fake post."
17 | },
18 | {
19 | "text": "Love it?"
20 | },
21 | {
22 | "text": "I bet you do!"
23 | }
24 | ]
25 | }
26 | ```
27 |
28 | Api endpoint: `https://api.myjson.com/bins/19dtxc`
29 |
30 | Here's [how I built the Medium Clap clone](https://medium.freecodecamp.org/how-i-re-built-the-medium-clap-effect-and-what-i-got-out-of-the-experiment-991672995fdf)
31 |
32 | To run app:
33 |
34 | ```
35 | 1. git clone https://github.com/ohansemmanuel/fake-medium.git
36 |
37 | 2. cd fake-medium
38 |
39 | 3. yarn install
40 |
41 | 4. yarn run start
42 | ```
43 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fake-medium",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.18.0",
7 | "mo-js": "^0.288.2",
8 | "react": "16.8.6",
9 | "react-dom": "16.8.6",
10 | "react-redux": "^5.0.7",
11 | "react-scripts": "1.1.4",
12 | "redux": "^4.0.0",
13 | "redux-thunk": "^2.3.0",
14 | "scheduler": "^0.14.0",
15 | "styled-components": "^3.4.1"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test --env=jsdom",
21 | "eject": "react-scripts eject"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/05-The-Profiler/fake-medium/public/favicon.ico
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
24 | React App
25 |
26 |
27 |
28 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/actions/api.js:
--------------------------------------------------------------------------------
1 | import { API_START, API_END, ACCESS_DENIED, API_ERROR } from "../actions/types";
2 |
3 | export const apiStart = label => ({
4 | type: API_START,
5 | payload: label
6 | });
7 |
8 | export const apiEnd = label => ({
9 | type: API_END,
10 | payload: label
11 | });
12 |
13 | export const accessDenied = url => ({
14 | type: ACCESS_DENIED,
15 | payload: {
16 | url
17 | }
18 | });
19 |
20 | export const apiError = error => ({
21 | type: API_ERROR,
22 | error
23 | });
24 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import { SET_ARTICLE_DETAILS, API, FETCH_ARTICLE_DETAILS } from "./types";
2 |
3 | export function fetchArticleDetails() {
4 | return apiAction({
5 | url: "https://api.myjson.com/bins/19dtxc",
6 | onSuccess: setArticleDetails,
7 | onFailure: () => console.log("Error occured loading articles"),
8 | label: FETCH_ARTICLE_DETAILS
9 | });
10 | }
11 |
12 | function setArticleDetails(data) {
13 | return {
14 | type: SET_ARTICLE_DETAILS,
15 | payload: data
16 | };
17 | }
18 |
19 | function apiAction({
20 | url = "",
21 | method = "GET",
22 | data = null,
23 | accessToken = null,
24 | onSuccess = () => {},
25 | onFailure = () => {},
26 | label = "",
27 | headersOverride = null
28 | }) {
29 | return {
30 | type: API,
31 | payload: {
32 | url,
33 | method,
34 | data,
35 | accessToken,
36 | onSuccess,
37 | onFailure,
38 | label,
39 | headersOverride
40 | }
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const FETCH_ARTICLE_DETAILS = "FETCH_ARTICLE_DETAILS";
2 | export const SET_ARTICLE_DETAILS = "SET_ARTICLE_DETAILS";
3 |
4 | export const API = "API";
5 | export const API_START = "API_START";
6 | export const API_END = "API_END";
7 | export const ACCESS_DENIED = "ACCESS_DENIED";
8 | export const API_ERROR = "API_ERROR";
9 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/components/Article.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const StyledArticle = styled.div`
5 | h1 {
6 | font-weight: 700;
7 | font-size: 36px;
8 | color: #000000;
9 | margin: 0;
10 | }
11 | h4 {
12 | font-weight: 700;
13 | font-size: 20px;
14 | color: #000000;
15 | margin: 0.5rem 0 4rem 0;
16 | }
17 | p {
18 | font-weight: 300;
19 | font-size: 18px;
20 | color: #4a4a4a;
21 | }
22 | `;
23 |
24 | const Article = ({ title = "", subtitle = "", paragraphs = [] }) => {
25 | return (
26 |
27 | {title}
28 | {subtitle}
29 | {paragraphs.map(paragraph => (
30 | {paragraph.text}
31 | ))}
32 |
33 | );
34 | };
35 |
36 | export default Article;
37 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/components/Clap.css:
--------------------------------------------------------------------------------
1 | /*========================
2 | BASIC styles
3 | =======================*/
4 | * {
5 | box-sizing: border-box;
6 | }
7 |
8 | /*========================
9 | BUTTON styles
10 | =======================*/
11 | .clap {
12 | position: relative;
13 | outline: 1px solid transparent;
14 | border-radius: 50%;
15 | border: 1px solid #bdc3c7;
16 | width: 80px;
17 | height: 80px;
18 | background: none;
19 | }
20 | .clap:after {
21 | content: "";
22 | position: absolute;
23 | top: 0;
24 | left: 0;
25 | display: block;
26 | border-radius: 50%;
27 | width: 79px;
28 | height: 79px;
29 | }
30 | .clap:hover {
31 | cursor: pointer;
32 | border: 1px solid #27ae60;
33 | transition: border-color 0.3s ease-in;
34 | }
35 | .clap:hover:after {
36 | animation: shockwave 1s ease-in infinite;
37 | }
38 | .clap svg {
39 | width: 40px;
40 | fill: none;
41 | stroke: #27ae60;
42 | stroke-width: 2px;
43 | }
44 | .clap svg.checked {
45 | fill: #27ae60;
46 | stroke: #fff;
47 | stroke-width: 1px;
48 | }
49 | .clap .clap--count {
50 | position: absolute;
51 | top: -50px;
52 | left: 20px;
53 | font-size: 0.8rem;
54 | color: white;
55 | background: #27ae60;
56 | border-radius: 50%;
57 | height: 40px;
58 | width: 40px;
59 | line-height: 40px;
60 | }
61 | .clap .clap--count-total {
62 | position: absolute;
63 | font-size: 0.8rem;
64 | width: 80px;
65 | text-align: center;
66 | left: 0;
67 | top: -22.8571428571px;
68 | color: #bdc3c7;
69 | }
70 |
71 | /*====================
72 | Message
73 | ======================*/
74 | #message {
75 | position: absolute;
76 | bottom: 20px;
77 | color: #27ae60;
78 | line-height: 1.52rem;
79 | padding: 1rem;
80 | font-size: 0.9rem;
81 | }
82 | #message a {
83 | color: #bdc3c7;
84 | }
85 |
86 | @keyframes shockwave {
87 | 0% {
88 | transform: scale(1);
89 | box-shadow: 0 0 2px #27ae60;
90 | opacity: 1;
91 | }
92 | 100% {
93 | transform: scale(1);
94 | opacity: 0;
95 | box-shadow: 0 0 50px #145b32, inset 0 0 10px #27ae60;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import styled from "styled-components";
4 | import Article from "../components/Article";
5 | import Clap from "../components/Clap";
6 | import { fetchArticleDetails } from "../actions";
7 |
8 | const StyledApp = styled.div`
9 | min-height: 100vh;
10 | display: flex;
11 | align-items: center;
12 |
13 | aside {
14 | min-width: 35vh;
15 | display: flex;
16 | justify-content: flex-end;
17 | }
18 | main {
19 | flex: 1 0 350px;
20 | ${"" /* not responsive */} padding: 0 5rem;
21 | }
22 | `;
23 |
24 | class App extends Component {
25 | state = {};
26 | componentDidMount() {
27 | this.props.fetchArticleDetails();
28 | }
29 | render() {
30 | const { title, subtitle, paragraphs } = this.props.data;
31 | const countTotal = this.props.data.numberOfRecommends;
32 | return (
33 |
34 |
37 |
38 | {this.props.isLoadingData ? (
39 | "Loading . . ."
40 | ) : (
41 |
46 | )}
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | const mapStateToProps = ({ data = {}, isLoadingData = false }) => ({
54 | data,
55 | isLoadingData
56 | });
57 | export default connect(
58 | mapStateToProps,
59 | {
60 | fetchArticleDetails
61 | }
62 | )(App);
63 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: "Lato", sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from "react-redux";
4 | import "./index.css";
5 | import App from "./containers/App";
6 | import registerServiceWorker from "./registerServiceWorker";
7 | import store from "./store";
8 |
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById("root")
14 | );
15 | registerServiceWorker();
16 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/middleware/api.js:
--------------------------------------------------------------------------------
1 | // inspired by https://leanpub.com/redux-book
2 | import axios from "axios";
3 | import { API } from "../actions/types";
4 | import { accessDenied, apiError, apiStart, apiEnd } from "../actions/api";
5 |
6 | const apiMiddleware = ({ dispatch }) => next => action => {
7 | next(action);
8 |
9 | if (action.type !== API) return;
10 |
11 | const {
12 | url,
13 | method,
14 | data,
15 | accessToken,
16 | onSuccess,
17 | onFailure,
18 | label,
19 | headers
20 | } = action.payload;
21 | const dataOrParams = ["GET", "DELETE"].includes(method) ? "params" : "data";
22 |
23 | // axios default configs
24 | axios.defaults.baseURL = process.env.REACT_APP_BASE_URL || "";
25 | axios.defaults.headers.common["Content-Type"] = "application/json";
26 | axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`;
27 |
28 | if (label) {
29 | dispatch(apiStart(label));
30 | }
31 |
32 | axios
33 | .request({
34 | url,
35 | method,
36 | headers,
37 | [dataOrParams]: data
38 | })
39 | .then(({ data }) => {
40 | dispatch(onSuccess(data));
41 | })
42 | .catch(error => {
43 | dispatch(apiError(error));
44 | dispatch(onFailure(error));
45 |
46 | if (error.response && error.response.status === 403) {
47 | dispatch(accessDenied(window.location.pathname));
48 | }
49 | })
50 | .finally(() => {
51 | if (label) {
52 | dispatch(apiEnd(label));
53 | }
54 | });
55 | };
56 |
57 | export default apiMiddleware;
58 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_ARTICLE_DETAILS,
3 | API_START,
4 | API_END,
5 | FETCH_ARTICLE_DETAILS
6 | } from "../actions/types";
7 |
8 | export default function(state = {}, action) {
9 | console.log("action type => ", action.type);
10 | switch (action.type) {
11 | case SET_ARTICLE_DETAILS:
12 | return { data: action.payload };
13 | case API_START:
14 | if (action.payload === FETCH_ARTICLE_DETAILS) {
15 | return {
16 | ...state,
17 | isLoadingData: true
18 | };
19 | }
20 | break;
21 | case API_END:
22 | if (action.payload === FETCH_ARTICLE_DETAILS) {
23 | return {
24 | ...state,
25 | isLoadingData: false
26 | };
27 | }
28 | break;
29 | default:
30 | return state;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/05-The-Profiler/fake-medium/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from "redux";
2 | import rootReducer from "../reducers";
3 | import apiMiddleware from "../middleware/api";
4 |
5 | const store = createStore(rootReducer, applyMiddleware(apiMiddleware));
6 | window.store = store;
7 | export default store;
8 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/06-Lazy-Load/bank-app/README.md
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bank-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "format-number": "^3.0.0",
7 | "react": "^16.8.6",
8 | "react-dom": "^16.8.6",
9 | "react-scripts": "1.1.4"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test --env=jsdom",
15 | "eject": "react-scripts eject"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/06-Lazy-Load/bank-app/public/favicon.ico
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/Root.js:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import Login from './containers/Login'
3 | import UserProvider, { UserConsumer } from './context/UserContext'
4 |
5 | const App = React.lazy(() => import('./containers/App'))
6 |
7 | const Root = () => (
8 |
9 |
10 | {({ user, handleLogin }) =>
11 | user ? (
12 |
13 |
14 |
15 | ) : (
16 |
17 | )
18 | }
19 |
20 |
21 | )
22 | export default Root
23 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/api/index.js:
--------------------------------------------------------------------------------
1 | export const USER = {
2 | name: 'June',
3 | totalAmount: 2500701
4 | }
5 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/components/Charity.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 |
3 | const Charity = memo(() => {
4 | return Give away all your cash to charity
5 | })
6 |
7 | export default Charity
8 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/components/FormatAmount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import formatNumber from 'format-number'
3 |
4 | const FormatAmount = ({ totalAmount }) => {
5 | return {formatNumber({ prefix: '$' })(totalAmount)}
6 | }
7 |
8 | export default FormatAmount
9 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/components/Greeting.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { UserConsumer } from '../context/UserContext'
3 |
4 | const Greeting = () => {
5 | return (
6 |
7 | {({ user }) => Welcome, {user.name}!
}
8 |
9 | )
10 | }
11 |
12 | export default Greeting
13 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/components/TotalAmount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FormatAmount from './FormatAmount'
3 |
4 | const TotalAmount = ({ totalAmount }) => {
5 | return (
6 |
10 | )
11 | }
12 |
13 | export default TotalAmount
14 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/components/User.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import Greeting from './Greeting'
3 |
4 | const User = memo(({ profilePic }) => {
5 | return (
6 |
7 |

8 |
9 |
10 | )
11 | })
12 |
13 | User.displayName = 'User'
14 | export default User
15 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/components/ViewAccountBalance.js:
--------------------------------------------------------------------------------
1 | import React, { memo, Fragment } from 'react'
2 | import TotalAmount from './TotalAmount'
3 | import { UserConsumer } from '../context/UserContext'
4 |
5 | const ViewAccountBalance = memo(({ showBalance, displayBalance }) => {
6 | return (
7 |
8 | {!showBalance ? (
9 |
10 |
11 | Would you love to view your account balance?{' '}
12 |
13 |
16 |
17 | ) : (
18 |
19 | {({ user }) => }
20 |
21 | )}
22 |
23 | )
24 | })
25 |
26 | ViewAccountBalance.displayName = 'ViewAccountBalance'
27 | export default ViewAccountBalance
28 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/components/WithdrawButton.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import formatNumber from 'format-number'
3 | import { UserConsumer } from '../context/UserContext'
4 |
5 | const WithdrawButton = memo(({ amount }) => {
6 | return (
7 |
8 | {({ handleWithdrawal }) => (
9 |
16 | )}
17 |
18 | )
19 | })
20 |
21 | WithdrawButton.displayName = 'WithdrawButton'
22 | export default WithdrawButton
23 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/containers/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | min-height: 100%;
3 | text-align: center;
4 | background-image: linear-gradient(
5 | to bottom,
6 | rgba(255, 255, 255, 0) 50%,
7 | #fdf6f6 50%,
8 | #fdf6f6 100%
9 | );
10 | display: flex;
11 | flex-direction: column;
12 | justify-content: center;
13 | }
14 |
15 | .App__greeting {
16 | color: #fff;
17 | font-weight: bold;
18 | font-size: 2rem;
19 |
20 | }
21 | .App__userpic {
22 | border-radius: 50%;
23 | width: 100px;
24 | height: 100px;
25 | align-self: center;
26 | }
27 | .App__amount {
28 | position: relative;
29 | align-self: center;
30 | background: #fff;
31 | color: rgba(0, 0, 0, 0.47);
32 | font-size: 2.1rem;
33 | font-weight: bold;
34 | padding: 2.5rem 5rem 1.5rem;
35 | border-radius: 11px;
36 | margin: 1rem 0 5rem 0;
37 | box-shadow: 0 10px 5px 5px rgba(0, 0, 0, 0.02);
38 | animation-duration: 0.75s;
39 | animation-name: bounceIn;
40 | }
41 | .App__amount:after {
42 | content: "";
43 | display: block;
44 | position: absolute;
45 | z-index: -1;
46 | width: 90%;
47 | height: 100%;
48 | top: 10%;
49 | left: 5%;
50 | background: #fff;
51 | box-shadow: 0 5px 34px 23px rgba(0, 0, 0, 0.06);
52 | border-radius: 11px;
53 | }
54 | .App__amount--info {
55 | font-size: 0.6rem;
56 | }
57 |
58 | .App__button {
59 | outline: 0;
60 | background: #fff;
61 | color: #fe718f;
62 | font-size: 1rem;
63 | padding: 1rem;
64 | margin: 1rem;
65 | border: 0;
66 | box-shadow: none;
67 | font-weight: bold;
68 | cursor: pointer;
69 | }
70 |
71 | .App__giveaway {
72 | font-size: 0.7rem;
73 | color: #fe8183;
74 | cursor: pointer;
75 | }
76 |
77 | .App__showBalance {
78 | opacity: 0.65;
79 | }
80 |
81 | .App__showBalanceCTA {
82 | margin: 1rem 0;
83 | }
84 |
85 | @keyframes bounceIn {
86 | from,
87 | 20%,
88 | 40%,
89 | 60%,
90 | 80%,
91 | to {
92 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
93 | }
94 |
95 | 0% {
96 | opacity: 0;
97 | transform: scale3d(0.3, 0.3, 0.3);
98 | }
99 |
100 | 20% {
101 | transform: scale3d(1.1, 1.1, 1.1);
102 | }
103 |
104 | 40% {
105 | transform: scale3d(0.9, 0.9, 0.9);
106 | }
107 |
108 | 60% {
109 | opacity: 1;
110 | transform: scale3d(1.03, 1.03, 1.03);
111 | }
112 |
113 | 80% {
114 | transform: scale3d(0.97, 0.97, 0.97);
115 | }
116 |
117 | to {
118 | opacity: 1;
119 | transform: scale3d(1, 1, 1);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import User from '../components/User'
3 | import WithdrawButton from '../components/WithdrawButton'
4 | import ViewAccountBalance from '../components/ViewAccountBalance'
5 | import Charity from '../components/Charity'
6 | import photographer from '../images/girl.png'
7 |
8 | import './App.css'
9 |
10 | class App extends PureComponent {
11 | state = {
12 | showBalance: false
13 | }
14 |
15 | displayBalance = () => {
16 | this.setState({ showBalance: true })
17 | }
18 | render () {
19 | const { showBalance } = this.state
20 |
21 | return (
22 |
23 |
24 |
25 |
29 |
30 |
34 |
35 |
36 |
37 | )
38 | }
39 | }
40 |
41 | export default App
42 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/containers/Login.css:
--------------------------------------------------------------------------------
1 | .Login {
2 | min-height: 100vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | flex-direction: column
7 | }
8 |
9 | .Login label {
10 | display: block;
11 | margin: 0.6rem 0;
12 | }
13 |
14 | .Login input {
15 | padding: 0.5rem;
16 | margin-bottom: 0.6rem;
17 | min-height: 40px;
18 | font-size: 16px;
19 | min-width: 300px;
20 | border: 0;
21 | border-radius: 4px;
22 | }
23 | .Login button[type='submit'] {
24 | width: 300px;
25 | outline: 0;
26 | background: #fff;
27 | color: #fe718f;
28 | font-size: 1rem;
29 | padding: 1rem;
30 | margin: 1.5rem;
31 | border: 0;
32 | box-shadow: none;
33 | font-weight: bold;
34 | cursor: pointer;
35 | }
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/containers/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import './Login.css'
4 |
5 | class Login extends Component {
6 | state = {}
7 | handleFormInput = evt => {
8 | const { name, value } = evt.target
9 | this.setState({
10 | [name]: [value]
11 | })
12 | }
13 |
14 | render () {
15 | const { username = '', password = '' } = this.state
16 | const { handleLogin } = this.props
17 | return (
18 |
40 | )
41 | }
42 | }
43 |
44 | export default Login
45 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/context/UserContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, Component } from 'react'
2 | import { USER } from '../api'
3 |
4 | const { Provider, Consumer } = createContext()
5 |
6 | class UserProvider extends Component {
7 | constructor () {
8 | super()
9 | this.state = {
10 | user: null,
11 | handleLogin: this.handleLogin,
12 | handleWithdrawal: this.handleWithdrawal
13 | }
14 | }
15 |
16 | handleLogin = evt => {
17 | evt.preventDefault()
18 | this.setState({
19 | user: USER
20 | })
21 | }
22 |
23 | handleWithdrawal = evt => {
24 | const { name, totalAmount } = this.state.user
25 | const withdrawalAmount = evt.target.dataset.amount
26 |
27 | this.setState({
28 | user: {
29 | name,
30 | totalAmount: totalAmount - withdrawalAmount
31 | }
32 | })
33 | }
34 |
35 | render () {
36 | return {this.props.children}
37 | }
38 | }
39 |
40 | export { UserProvider as default, Consumer as UserConsumer }
41 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/images/girl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/06-Lazy-Load/bank-app/src/images/girl.png
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | width: 100%;
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 | *,
9 | *:before,
10 | *:after {
11 | box-sizing: border-box;
12 | }
13 |
14 | body {
15 | font-family: sans-serif;
16 | font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, “Roboto”, “Oxygen”,
17 | “Ubuntu”, “Cantarell”, “Fira Sans”, “Droid Sans”, “Helvetica Neue”,
18 | sans-serif;
19 | background-image: radial-gradient(#fe718f 0%, #fe8183 42%, #ff9b73 87%);
20 | }
21 |
22 | #root {
23 | height: 100%;
24 | }
25 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import Root from './Root'
5 | import registerServiceWorker from './registerServiceWorker'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 | registerServiceWorker()
9 |
--------------------------------------------------------------------------------
/06-Lazy-Load/bank-app/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "compound-component",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-dom": "^16.8.6",
8 | "react-scripts": "3.0.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/08-Advanced-Hook-Patterns/compound-component/public/favicon.ico
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | display: flex;
4 | flex-direction: column;
5 | min-height: 100vh;
6 | align-items: center;
7 | padding-top: 2rem;
8 | }
9 |
10 | .App-logo {
11 | animation: App-logo-spin infinite 20s linear;
12 | height: 40vmin;
13 | pointer-events: none;
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './App.css'
3 | import Expandable from './components/Expandable'
4 |
5 | function App () {
6 | return (
7 |
8 | {/* ==================
9 | uncomment the next lines to see first example
10 | ================== */}
11 |
12 | {/*
13 | React hooks
14 |
15 | Hooks are awesome
16 | */}
17 |
18 |
19 |
20 | Reintroducing React
21 |
22 |
23 |
24 |
29 |
30 | This book is so f*cking amazing!
31 |
36 | Go get it now.
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default App
46 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Body.css:
--------------------------------------------------------------------------------
1 | .Expandable-panel {
2 | margin: 0;
3 | padding: 1em 1.5em;
4 | border: 1px solid hsl(216, 94%, 94%);;
5 | min-height: 150px;
6 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Body.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { ExpandableContext } from './Expandable'
3 | import './Body.css'
4 |
5 | const Body = ({ children, className = '', ...otherProps }) => {
6 | const { expanded } = useContext(ExpandableContext)
7 | const combinedClassNames = ['Expandable-panel', className].join('')
8 |
9 | return expanded ? (
10 |
11 | {children}
12 |
13 | ) : null
14 | }
15 |
16 | export default Body
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Expandable.css:
--------------------------------------------------------------------------------
1 | .Expandable {
2 | position: relative;
3 | width: 350px;
4 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Expandable.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useState,
4 | useMemo,
5 | useCallback,
6 | useEffect,
7 | useRef
8 | } from 'react'
9 | import Header from './Header'
10 | import Icon from './Icon'
11 | import Body from './Body'
12 |
13 | import './Expandable.css'
14 |
15 | export const ExpandableContext = createContext()
16 | const { Provider } = ExpandableContext
17 |
18 | const Expandable = ({ children, onExpand, className = '', ...otherProps }) => {
19 | const [expanded, setExpanded] = useState(false)
20 | const toggle = useCallback(
21 | () => setExpanded(prevExpanded => !prevExpanded),
22 | []
23 | )
24 |
25 | const componentJustMounted = useRef(true)
26 | useEffect(
27 | () => {
28 | if (!componentJustMounted.current) {
29 | onExpand(expanded)
30 | }
31 | componentJustMounted.current = false
32 | },
33 | [expanded]
34 | )
35 |
36 | const value = useMemo(() => ({ expanded, toggle }), [expanded, toggle])
37 | const combinedClassNames = ['Expandable', className].join('')
38 |
39 | return (
40 |
41 |
42 | {children}
43 |
44 |
45 | )
46 | }
47 |
48 | Expandable.Header = Header
49 | Expandable.Body = Body
50 | Expandable.Icon = Icon
51 |
52 | export default Expandable
53 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Header.css:
--------------------------------------------------------------------------------
1 | .Expandable-trigger {
2 | background: none;
3 | color: hsl(0, 0%, 13%);
4 | display: block;
5 | font-size: 1rem;
6 | font-weight: normal;
7 | margin: 0;
8 | padding: 1em 1.5em;
9 | position: relative;
10 | text-align: left;
11 | width: 100%;
12 | outline: none;
13 | text-align: center;
14 | }
15 |
16 | .Expandable-trigger:focus,
17 | .Expandable-trigger:hover {
18 | background: hsl(216, 94%, 94%);
19 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { ExpandableContext } from './Expandable'
3 |
4 | import './Header.css'
5 |
6 | const Header = ({ children, className = '', ...otherProps }) => {
7 | const { toggle } = useContext(ExpandableContext)
8 |
9 | // combine our internal className and any other provided by the user
10 | const combinedClassName = ['Expandable-trigger', className].join('')
11 |
12 | return (
13 |
16 | )
17 | }
18 |
19 | export default Header
20 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Icon.css:
--------------------------------------------------------------------------------
1 | .Expandable-icon {
2 | position: absolute;
3 | top: 16px;
4 | right: 10px;
5 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { ExpandableContext } from './Expandable'
3 | import './Icon.css'
4 |
5 | const Icon = ({ className = '', ...otherProps }) => {
6 | const { expanded } = useContext(ExpandableContext)
7 | const combinedClassNames = ['Expandable-icon', className].join('')
8 |
9 | return (
10 |
11 | {expanded ? '-' : '+'}
12 |
13 | )
14 | }
15 |
16 | export default Icon
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/compound-component/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "control-props",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-dom": "^16.8.6",
8 | "react-scripts": "3.0.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/08-Advanced-Hook-Patterns/control-props/public/favicon.ico
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | display: flex;
4 | flex-direction: column;
5 | min-height: 100vh;
6 | align-items: center;
7 | padding-top: 2rem;
8 | }
9 |
10 | .App-logo {
11 | animation: App-logo-spin infinite 20s linear;
12 | height: 40vmin;
13 | pointer-events: none;
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import './App.css'
3 | import Expandable from './components/Expandable'
4 |
5 | const information = [
6 | {
7 | header: 'Why everyone should live forrever',
8 | note: 'This is highly sensitive information on how to prevent death!!!!'
9 | },
10 | {
11 | header: 'The internet disappears',
12 | note:
13 | 'I just uncovered the biggest threat to the internet. The internet disappears in 301 seconds. Save yourself'
14 | },
15 | {
16 | header: 'The truth about Elon musk and Mars!',
17 | note: 'Nobody tells you this. Elon musk ... News coming soon.'
18 | }
19 | ]
20 |
21 | function App () {
22 | const [activeIndex, setActiveIndex] = useState(null)
23 | const onExpand = evt => setActiveIndex(evt.target.dataset.index)
24 |
25 | return (
26 |
27 | {information.map(({ header, note }, index) => (
28 |
33 |
37 | {header}
38 |
39 |
40 | {note}
41 |
42 | ))}
43 |
44 | )
45 | }
46 |
47 | export default App
48 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Body.css:
--------------------------------------------------------------------------------
1 | .Expandable-panel {
2 | margin: 0;
3 | padding: 1em 1.5em;
4 | border: 1px solid hsl(216, 94%, 94%);;
5 | min-height: 150px;
6 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Body.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { ExpandableContext } from './Expandable'
3 | import './Body.css'
4 |
5 | const Body = ({ children, className = '', ...otherProps }) => {
6 | const { expanded } = useContext(ExpandableContext)
7 | const combinedClassNames = ['Expandable-panel', className].join('')
8 |
9 | return expanded ? (
10 |
11 | {children}
12 |
13 | ) : null
14 | }
15 |
16 | export default Body
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Expandable.css:
--------------------------------------------------------------------------------
1 | .Expandable {
2 | position: relative;
3 | width: 350px;
4 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Expandable.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useState,
4 | useMemo,
5 | useCallback,
6 | useEffect,
7 | useRef
8 | } from 'react'
9 | import Header from './Header'
10 | import Icon from './Icon'
11 | import Body from './Body'
12 |
13 | import './Expandable.css'
14 |
15 | export const ExpandableContext = createContext()
16 | const { Provider } = ExpandableContext
17 |
18 | const Expandable = ({
19 | children,
20 | onExpand,
21 | shouldExpand,
22 | className = '',
23 | ...otherProps
24 | }) => {
25 | const isExpandControlled = shouldExpand !== undefined
26 | const [expanded, setExpanded] = useState(false)
27 | const getState = isExpandControlled ? shouldExpand : expanded
28 |
29 | const toggle = useCallback(
30 | () => setExpanded(prevExpanded => !prevExpanded),
31 | []
32 | )
33 | const getToggle = isExpandControlled ? onExpand : toggle
34 |
35 | const componentJustMounted = useRef(true)
36 | useEffect(
37 | () => {
38 | if (!componentJustMounted.current && !isExpandControlled) {
39 | onExpand(expanded)
40 | }
41 | componentJustMounted.current = false
42 | },
43 | [expanded, onExpand, isExpandControlled]
44 | )
45 |
46 | const value = useMemo(() => ({ expanded: getState, toggle: getToggle }), [
47 | getState,
48 | getToggle
49 | ])
50 | const combinedClassNames = ['Expandable', className].join('')
51 |
52 | return (
53 |
54 |
55 | {children}
56 |
57 |
58 | )
59 | }
60 |
61 | Expandable.Header = Header
62 | Expandable.Body = Body
63 | Expandable.Icon = Icon
64 |
65 | export default Expandable
66 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Header.css:
--------------------------------------------------------------------------------
1 | .Expandable-trigger {
2 | background: none;
3 | color: hsl(0, 0%, 13%);
4 | display: block;
5 | font-size: 1rem;
6 | font-weight: normal;
7 | margin: 0;
8 | padding: 1em 1.5em;
9 | position: relative;
10 | text-align: left;
11 | width: 100%;
12 | outline: none;
13 | text-align: center;
14 | }
15 |
16 | .Expandable-trigger:focus,
17 | .Expandable-trigger:hover {
18 | background: hsl(216, 94%, 94%);
19 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { ExpandableContext } from './Expandable'
3 |
4 | import './Header.css'
5 |
6 | const Header = ({ children, className = '', ...otherProps }) => {
7 | const { toggle } = useContext(ExpandableContext)
8 |
9 | // combine our internal className and any other provided by the user
10 | const combinedClassName = ['Expandable-trigger', className].join('')
11 |
12 | return (
13 |
16 | )
17 | }
18 |
19 | export default Header
20 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Icon.css:
--------------------------------------------------------------------------------
1 | .Expandable-icon {
2 | position: absolute;
3 | top: 16px;
4 | right: 10px;
5 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { ExpandableContext } from './Expandable'
3 | import './Icon.css'
4 |
5 | const Icon = ({ className = '', ...otherProps }) => {
6 | const { expanded } = useContext(ExpandableContext)
7 | const combinedClassNames = ['Expandable-icon', className].join('')
8 |
9 | return (
10 |
11 | {expanded ? '-' : '+'}
12 |
13 | )
14 | }
15 |
16 | export default Icon
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/control-props/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prop-collection",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-dom": "^16.8.6",
8 | "react-scripts": "3.0.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/08-Advanced-Hook-Patterns/prop-collection/public/favicon.ico
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | display: flex;
4 | flex-direction: column;
5 | min-height: 100vh;
6 | align-items: center;
7 | padding-top: 2rem;
8 | }
9 |
10 | .App-logo {
11 | animation: App-logo-spin infinite 20s linear;
12 | height: 40vmin;
13 | pointer-events: none;
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import useExpanded, { useEffectAfterMount } from './components/Expandable'
3 | import Header from './components/Header'
4 | import Icon from './components/Icon'
5 | import Body from './components/Body'
6 |
7 | import './App.css'
8 | import './components/Expandable.css'
9 |
10 | function WithoutComponents () {
11 | const { expanded, togglerProps } = useExpanded()
12 |
13 | useEffectAfterMount(
14 | () => {
15 | console.log('Yay! button was clicked!!')
16 | },
17 | [expanded]
18 | )
19 |
20 | return (
21 |
22 |
23 | {expanded ?
{'😎'.repeat(50)}
: null}
24 |
25 | )
26 | }
27 |
28 | function App () {
29 | return (
30 |
31 |
32 |
33 | {/* uncomment to see default UI */}
34 | {/* */}
35 |
36 | )
37 | }
38 |
39 | // eslint-disable-next-line no-unused-vars
40 | function WithComponent () {
41 | const { expanded, toggle } = useExpanded()
42 | return (
43 |
44 |
45 |
46 | React hooks is awesome!
47 |
48 | )
49 | }
50 |
51 | export default App
52 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Body.css:
--------------------------------------------------------------------------------
1 | .Expandable-panel {
2 | margin: 0;
3 | padding: 1em 1.5em;
4 | border: 1px solid hsl(216, 94%, 94%);;
5 | min-height: 150px;
6 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Body.css'
3 |
4 | const Body = ({ children, className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-panel', className].join('')
6 |
7 | return expanded ? (
8 |
9 | {children}
10 |
11 | ) : null
12 | }
13 |
14 | export default Body
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Expandable.css:
--------------------------------------------------------------------------------
1 | .Expandable {
2 | position: relative;
3 | width: 350px;
4 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Expandable.js:
--------------------------------------------------------------------------------
1 | import useExpanded from '../useExpanded'
2 | import useEffectAfterMount from '../useEffectAfterMount'
3 |
4 | export { useExpanded as default, useEffectAfterMount }
5 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Header.css:
--------------------------------------------------------------------------------
1 | .Expandable-trigger {
2 | background: none;
3 | color: hsl(0, 0%, 13%);
4 | display: block;
5 | font-size: 1rem;
6 | font-weight: normal;
7 | margin: 0;
8 | padding: 1em 1.5em;
9 | position: relative;
10 | text-align: left;
11 | width: 100%;
12 | outline: none;
13 | text-align: center;
14 | }
15 |
16 | .Expandable-trigger:focus,
17 | .Expandable-trigger:hover {
18 | background: hsl(216, 94%, 94%);
19 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './Header.css'
4 |
5 | const Header = ({ children, className = '', toggle, ...otherProps }) => {
6 | // combine our internal className and any other provided by the user
7 | const combinedClassName = ['Expandable-trigger', className].join('')
8 |
9 | return (
10 |
13 | )
14 | }
15 |
16 | export default Header
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Icon.css:
--------------------------------------------------------------------------------
1 | .Expandable-icon {
2 | position: absolute;
3 | top: 16px;
4 | right: 10px;
5 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Icon.css'
3 |
4 | const Icon = ({ className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-icon', className].join('')
6 |
7 | return (
8 |
9 | {expanded ? '-' : '+'}
10 |
11 | )
12 | }
13 |
14 | export default Icon
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/useEffectAfterMount.js:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react'
2 |
3 | export default function useEffectAfterMount (cb, deps) {
4 | const componentJustMounted = useRef(true)
5 | useEffect(() => {
6 | if (!componentJustMounted.current) {
7 | return cb()
8 | }
9 | componentJustMounted.current = false
10 |
11 | // eslint-disable-next-line react-hooks/exhaustive-deps
12 | }, deps)
13 | }
14 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-collection/src/useExpanded.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useState } from 'react'
2 |
3 | export default function useExpanded () {
4 | const [expanded, setExpanded] = useState(false)
5 | const toggle = useCallback(
6 | () => setExpanded(prevExpanded => !prevExpanded),
7 | []
8 | )
9 | const togglerProps = useMemo(
10 | () => ({
11 | onClick: toggle,
12 | 'aria-expanded': expanded
13 | }),
14 | [toggle, expanded]
15 | )
16 |
17 | const value = useMemo(() => ({ expanded, toggle, togglerProps }), [
18 | expanded,
19 | toggle,
20 | togglerProps
21 | ])
22 |
23 | return value
24 | }
25 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prop-getters",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-dom": "^16.8.6",
8 | "react-scripts": "3.0.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/08-Advanced-Hook-Patterns/prop-getters/public/favicon.ico
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | display: flex;
4 | flex-direction: column;
5 | min-height: 100vh;
6 | align-items: center;
7 | padding-top: 2rem;
8 | }
9 |
10 | .App-logo {
11 | animation: App-logo-spin infinite 20s linear;
12 | height: 40vmin;
13 | pointer-events: none;
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import useExpanded, { useEffectAfterMount } from './components/Expandable'
3 | import Header from './components/Header'
4 | import Icon from './components/Icon'
5 | import Body from './components/Body'
6 |
7 | import './App.css'
8 | import './components/Expandable.css'
9 |
10 | function WithoutComponents () {
11 | const { expanded, getTogglerProps } = useExpanded()
12 |
13 | useEffectAfterMount(
14 | () => {
15 | console.log('Yay! button was clicked!!')
16 | },
17 | [expanded]
18 | )
19 |
20 | const customClickHandler = () => {
21 | console.log('custom click handler!!!!!')
22 | }
23 |
24 | return (
25 |
26 |
35 | {expanded ?
{'😎'.repeat(50)}
: null}
36 |
37 | )
38 | }
39 |
40 | function App () {
41 | return (
42 |
43 |
44 |
45 | {/* uncomment to see default UI */}
46 | {/* */}
47 |
48 | )
49 | }
50 |
51 | // eslint-disable-next-line no-unused-vars
52 | function WithComponent () {
53 | const { expanded, toggle } = useExpanded()
54 | return (
55 |
56 |
57 |
58 | React hooks is awesome!
59 |
60 | )
61 | }
62 |
63 | export default App
64 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Body.css:
--------------------------------------------------------------------------------
1 | .Expandable-panel {
2 | margin: 0;
3 | padding: 1em 1.5em;
4 | border: 1px solid hsl(216, 94%, 94%);;
5 | min-height: 150px;
6 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Body.css'
3 |
4 | const Body = ({ children, className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-panel', className].join('')
6 |
7 | return expanded ? (
8 |
9 | {children}
10 |
11 | ) : null
12 | }
13 |
14 | export default Body
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Expandable.css:
--------------------------------------------------------------------------------
1 | .Expandable {
2 | position: relative;
3 | width: 350px;
4 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Expandable.js:
--------------------------------------------------------------------------------
1 | import useExpanded from '../useExpanded'
2 | import useEffectAfterMount from '../useEffectAfterMount'
3 |
4 | export { useExpanded as default, useEffectAfterMount }
5 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Header.css:
--------------------------------------------------------------------------------
1 | .Expandable-trigger {
2 | background: none;
3 | color: hsl(0, 0%, 13%);
4 | display: block;
5 | font-size: 1rem;
6 | font-weight: normal;
7 | margin: 0;
8 | padding: 1em 1.5em;
9 | position: relative;
10 | text-align: left;
11 | width: 100%;
12 | outline: none;
13 | text-align: center;
14 | }
15 |
16 | .Expandable-trigger:focus,
17 | .Expandable-trigger:hover {
18 | background: hsl(216, 94%, 94%);
19 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './Header.css'
4 |
5 | const Header = ({ children, className = '', toggle, ...otherProps }) => {
6 | // combine our internal className and any other provided by the user
7 | const combinedClassName = ['Expandable-trigger', className].join('')
8 |
9 | return (
10 |
13 | )
14 | }
15 |
16 | export default Header
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Icon.css:
--------------------------------------------------------------------------------
1 | .Expandable-icon {
2 | position: absolute;
3 | top: 16px;
4 | right: 10px;
5 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Icon.css'
3 |
4 | const Icon = ({ className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-icon', className].join('')
6 |
7 | return (
8 |
9 | {expanded ? '-' : '+'}
10 |
11 | )
12 | }
13 |
14 | export default Icon
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/useEffectAfterMount.js:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react'
2 |
3 | export default function useEffectAfterMount (cb, deps) {
4 | const componentJustMounted = useRef(true)
5 | useEffect(() => {
6 | if (!componentJustMounted.current) {
7 | return cb()
8 | }
9 | componentJustMounted.current = false
10 |
11 | // eslint-disable-next-line react-hooks/exhaustive-deps
12 | }, deps)
13 | }
14 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/prop-getters/src/useExpanded.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useState } from 'react'
2 |
3 | const callFunctionsInSequence = (...fns) => (...args) => {
4 | console.log(args)
5 | return fns.forEach(fn => fn && fn(...args))
6 | }
7 |
8 | export default function useExpanded () {
9 | const [expanded, setExpanded] = useState(false)
10 | const toggle = useCallback(
11 | () => setExpanded(prevExpanded => !prevExpanded),
12 | []
13 | )
14 | const getTogglerProps = useCallback(
15 | ({ onClick, ...props }) => ({
16 | 'aria-expanded': expanded,
17 | onClick: callFunctionsInSequence(toggle, onClick),
18 | ...props
19 | }),
20 | [toggle, expanded]
21 | )
22 |
23 | const value = useMemo(() => ({ expanded, toggle, getTogglerProps }), [
24 | expanded,
25 | toggle,
26 | getTogglerProps
27 | ])
28 |
29 | return value
30 | }
31 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "state-initializers",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-dom": "^16.8.6",
8 | "react-scripts": "3.0.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/08-Advanced-Hook-Patterns/state-initializers/public/favicon.ico
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | display: flex;
4 | flex-direction: column;
5 | min-height: 100vh;
6 | align-items: center;
7 | padding-top: 2rem;
8 | }
9 |
10 | .App-logo {
11 | animation: App-logo-spin infinite 20s linear;
12 | height: 40vmin;
13 | pointer-events: none;
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import useExpanded, { useEffectAfterMount } from './components/Expandable'
3 | import Header from './components/Header'
4 | import Icon from './components/Icon'
5 | import Body from './components/Body'
6 |
7 | import './App.css'
8 | import './components/Expandable.css'
9 | import { longText as TermsAndConditionText } from './components/utils'
10 |
11 | function App () {
12 | const { expanded, toggle, reset, resetDep } = useExpanded(false)
13 |
14 | useEffectAfterMount(
15 | () => {
16 | console.log('reset was invoked!!!!')
17 | },
18 | [resetDep]
19 | )
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | {TermsAndConditionText}
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default App
36 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Body.css:
--------------------------------------------------------------------------------
1 | .Expandable-panel {
2 | margin: 0;
3 | padding: 1em 1.5em;
4 | border: 1px solid hsl(216, 94%, 94%);;
5 | min-height: 150px;
6 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Body.css'
3 |
4 | const Body = ({ children, className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-panel', className].join('')
6 |
7 | return expanded ? (
8 |
9 | {children}
10 |
11 | ) : null
12 | }
13 |
14 | export default Body
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Expandable.css:
--------------------------------------------------------------------------------
1 | .Expandable {
2 | position: relative;
3 | width: 350px;
4 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Expandable.js:
--------------------------------------------------------------------------------
1 | import useExpanded from '../useExpanded'
2 | import useEffectAfterMount from '../useEffectAfterMount'
3 |
4 | export { useExpanded as default, useEffectAfterMount }
5 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Header.css:
--------------------------------------------------------------------------------
1 | .Expandable-trigger {
2 | background: none;
3 | color: hsl(0, 0%, 13%);
4 | display: block;
5 | font-size: 1rem;
6 | font-weight: normal;
7 | margin: 0;
8 | padding: 1em 1.5em;
9 | position: relative;
10 | text-align: left;
11 | width: 100%;
12 | outline: none;
13 | text-align: center;
14 | }
15 |
16 | .Expandable-trigger:focus,
17 | .Expandable-trigger:hover {
18 | background: hsl(216, 94%, 94%);
19 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './Header.css'
4 |
5 | const Header = ({ children, className = '', toggle, ...otherProps }) => {
6 | // combine our internal className and any other provided by the user
7 | const combinedClassName = ['Expandable-trigger', className].join('')
8 |
9 | return (
10 |
13 | )
14 | }
15 |
16 | export default Header
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Icon.css:
--------------------------------------------------------------------------------
1 | .Expandable-icon {
2 | position: absolute;
3 | top: 16px;
4 | right: 10px;
5 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Icon.css'
3 |
4 | const Icon = ({ className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-icon', className].join('')
6 |
7 | return (
8 |
9 | {expanded ? '-' : '+'}
10 |
11 | )
12 | }
13 |
14 | export default Icon
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/components/utils.js:
--------------------------------------------------------------------------------
1 | export const longText = `Lorem ipsum dolor sit amet, et noster prodesset moderatius nam. Omnium postulant qui ea, usu veniam vivendo tibique ei. Ei vim elit debet tibique. Ea utroque graecis omittantur mea, vocibus singulis id pri, nec no audire definiebas.
2 |
3 | Vim harum maiestatis scriptorem ad, vix mundi dicant te. Dolorem minimum torquatos est cu, equidem veritus usu no, ut his purto dicit populo. Omnes fabellas no qui, utamur detraxit id ius. Et nisl posidonium pri. In laudem possim eum, quo ridens periculis neglegentur id.
4 |
5 | Ne eos quas deleniti, ut vis tantas laudem aliquip. Duo virtute vulputate vituperata ea, facilisi efficiendi ea pri. Eu mea accumsan mentitum probatus, quando regione sed ut. Propriae persecuti disputationi no vim, his assueverit scripserit necessitatibus eu. Assentior sententiae ne duo, ei vix inani facilisi sadipscing. Paulo facete nec ad.
6 |
7 | Aliquip definitiones vix et. Usu esse percipitur quaerendum ut, solet aliquid antiopam te his. Quaeque voluptaria vituperatoribus an eos, in nostrum convenire mea. Ei viris epicuri eam, sit ut dolorum delectus ocurreret. Et sit diam nostro, pro ei volutpat quaerendum neglegentur, ea his dicam postulant. Pro id case aliquando, accusam fabellas nec et.
8 |
9 | Enim utroque ei vel, his ei sint eirmod veritus. Ut eum error aliquip assentior. Sea in bonorum recteque, an pro partem volutpat. Brute debet pericula et usu. Appellantur adversarium mel in.`
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/useEffectAfterMount.js:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react'
2 |
3 | export default function useEffectAfterMount (cb, deps) {
4 | const componentJustMounted = useRef(true)
5 | useEffect(() => {
6 | if (!componentJustMounted.current) {
7 | return cb()
8 | }
9 | componentJustMounted.current = false
10 |
11 | // eslint-disable-next-line react-hooks/exhaustive-deps
12 | }, deps)
13 | }
14 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-initializers/src/useExpanded.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useState, useRef } from 'react'
2 |
3 | const callFunctionsInSequence = (...fns) => (...args) => {
4 | console.log(args)
5 | return fns.forEach(fn => fn && fn(...args))
6 | }
7 |
8 | export default function useExpanded (initialExpanded = false) {
9 | const [expanded, setExpanded] = useState(initialExpanded)
10 | const toggle = useCallback(
11 | () => setExpanded(prevExpanded => !prevExpanded),
12 | []
13 | )
14 |
15 | const resetRef = useRef(0)
16 | const reset = useCallback(
17 | () => {
18 | setExpanded(initialExpanded)
19 | ++resetRef.current
20 | },
21 | [initialExpanded]
22 | )
23 | // const [resetDep, setResetDep] = useState(0)
24 | // const reset = useCallback(
25 | // () => {
26 | // setExpanded(initialExpanded)
27 | // setResetDep(resetDep => resetDep + 1)
28 | // },
29 | // [initialExpanded]
30 | // )
31 |
32 | const getTogglerProps = useCallback(
33 | ({ onClick, ...props }) => ({
34 | 'aria-expanded': expanded,
35 | onClick: callFunctionsInSequence(toggle, onClick),
36 | ...props
37 | }),
38 | [toggle, expanded]
39 | )
40 |
41 | const value = useMemo(
42 | () => ({
43 | expanded,
44 | toggle,
45 | getTogglerProps,
46 | reset,
47 | resetDep: resetRef.current
48 | }),
49 | [expanded, toggle, getTogglerProps, reset]
50 | )
51 |
52 | return value
53 | }
54 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "state-reducer",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-dom": "^16.8.6",
8 | "react-scripts": "3.0.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohansemmanuel/Reintroducing-react/d6b89c1c6a2c4349df4ec6affbd97c3e2ffb6139/08-Advanced-Hook-Patterns/state-reducer/public/favicon.ico
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | display: flex;
4 | flex-direction: column;
5 | min-height: 100vh;
6 | align-items: center;
7 | padding-top: 2rem;
8 | }
9 |
10 | .App-logo {
11 | animation: App-logo-spin infinite 20s linear;
12 | height: 40vmin;
13 | pointer-events: none;
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import useExpanded, { useEffectAfterMount } from './components/Expandable'
3 | import Header from './components/Header'
4 | import Icon from './components/Icon'
5 | import Body from './components/Body'
6 |
7 | import './App.css'
8 | import './components/Expandable.css'
9 |
10 | /**
11 | * useExpanded consumer builds an App to share a conspiracy.
12 | * Goal:
13 | * - after the user clicks to see the conspiracy ...
14 | * - the reset callback should be invoked
15 | * - the user should NOT be able to expand the container any longer
16 | * - the secret can only be viewed ONCE!!!
17 | */
18 |
19 | function App () {
20 | const hasViewedSecret = useRef(false)
21 | const { expanded, toggle, override, reset, resetDep } = useExpanded(
22 | false,
23 | appReducer
24 | )
25 | function appReducer (currentInternalState, action) {
26 | if (
27 | hasViewedSecret.current &&
28 | action.type === useExpanded.types.toggleExpand
29 | ) {
30 | return {
31 | ...action.internalChanges,
32 | // override internal update
33 | expanded: false
34 | }
35 | }
36 | return action.internalChanges
37 | }
38 |
39 | useEffectAfterMount(
40 | () => {
41 | // open secret in new tab 👇
42 | window.open('https://leanpub.com/reintroducing-react', '_blank')
43 | hasViewedSecret.current = true
44 | // perform side effect here 👉 e.g persist user details to database
45 | },
46 | [resetDep]
47 | )
48 |
49 | return (
50 |
51 |
52 |
53 | {' '}
54 | They've been lying to you{' '}
55 |
56 |
57 |
58 |
59 | This is highly sensitive information and can only be viewed ONCE!!!!
60 |
61 |
62 | Click to view the conspiracy
63 |
64 |
65 |
66 |
67 | {hasViewedSecret.current && (
68 |
69 | )}
70 |
71 | )
72 | }
73 |
74 | export default App
75 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Body.css:
--------------------------------------------------------------------------------
1 | .Expandable-panel {
2 | margin: 0;
3 | padding: 1em 1.5em;
4 | border: 1px solid hsl(216, 94%, 94%);;
5 | min-height: 150px;
6 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Body.css'
3 |
4 | const Body = ({ children, className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-panel', className].join('')
6 |
7 | return expanded ? (
8 |
9 | {children}
10 |
11 | ) : null
12 | }
13 |
14 | export default Body
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Expandable.css:
--------------------------------------------------------------------------------
1 | .Expandable {
2 | position: relative;
3 | width: 350px;
4 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Expandable.js:
--------------------------------------------------------------------------------
1 | import useExpanded from '../useExpanded'
2 | import useEffectAfterMount from '../useEffectAfterMount'
3 |
4 | export { useExpanded as default, useEffectAfterMount }
5 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Header.css:
--------------------------------------------------------------------------------
1 | .Expandable-trigger {
2 | background: none;
3 | color: hsl(0, 0%, 13%);
4 | display: block;
5 | font-size: 1rem;
6 | font-weight: normal;
7 | margin: 0;
8 | padding: 1em 1.5em;
9 | position: relative;
10 | text-align: left;
11 | width: 100%;
12 | outline: none;
13 | text-align: center;
14 | }
15 |
16 | .Expandable-trigger:focus,
17 | .Expandable-trigger:hover {
18 | background: hsl(216, 94%, 94%);
19 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './Header.css'
4 |
5 | const Header = ({ children, className = '', toggle, ...otherProps }) => {
6 | // combine our internal className and any other provided by the user
7 | const combinedClassName = ['Expandable-trigger', className].join('')
8 |
9 | return (
10 |
13 | )
14 | }
15 |
16 | export default Header
17 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Icon.css:
--------------------------------------------------------------------------------
1 | .Expandable-icon {
2 | position: absolute;
3 | top: 16px;
4 | right: 10px;
5 | }
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Icon.css'
3 |
4 | const Icon = ({ className = '', expanded, ...otherProps }) => {
5 | const combinedClassNames = ['Expandable-icon', className].join('')
6 |
7 | return (
8 |
9 | {expanded ? '-' : '+'}
10 |
11 | )
12 | }
13 |
14 | export default Icon
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/components/utils.js:
--------------------------------------------------------------------------------
1 | export const longText = `Lorem ipsum dolor sit amet, et noster prodesset moderatius nam. Omnium postulant qui ea, usu veniam vivendo tibique ei. Ei vim elit debet tibique. Ea utroque graecis omittantur mea, vocibus singulis id pri, nec no audire definiebas.
2 |
3 | Vim harum maiestatis scriptorem ad, vix mundi dicant te. Dolorem minimum torquatos est cu, equidem veritus usu no, ut his purto dicit populo. Omnes fabellas no qui, utamur detraxit id ius. Et nisl posidonium pri. In laudem possim eum, quo ridens periculis neglegentur id.
4 |
5 | Ne eos quas deleniti, ut vis tantas laudem aliquip. Duo virtute vulputate vituperata ea, facilisi efficiendi ea pri. Eu mea accumsan mentitum probatus, quando regione sed ut. Propriae persecuti disputationi no vim, his assueverit scripserit necessitatibus eu. Assentior sententiae ne duo, ei vix inani facilisi sadipscing. Paulo facete nec ad.
6 |
7 | Aliquip definitiones vix et. Usu esse percipitur quaerendum ut, solet aliquid antiopam te his. Quaeque voluptaria vituperatoribus an eos, in nostrum convenire mea. Ei viris epicuri eam, sit ut dolorum delectus ocurreret. Et sit diam nostro, pro ei volutpat quaerendum neglegentur, ea his dicam postulant. Pro id case aliquando, accusam fabellas nec et.
8 |
9 | Enim utroque ei vel, his ei sint eirmod veritus. Ut eum error aliquip assentior. Sea in bonorum recteque, an pro partem volutpat. Brute debet pericula et usu. Appellantur adversarium mel in.`
10 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/useEffectAfterMount.js:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react'
2 |
3 | export default function useEffectAfterMount (cb, deps) {
4 | const componentJustMounted = useRef(true)
5 | useEffect(() => {
6 | if (!componentJustMounted.current) {
7 | return cb()
8 | }
9 | componentJustMounted.current = false
10 |
11 | // eslint-disable-next-line react-hooks/exhaustive-deps
12 | }, deps)
13 | }
14 |
--------------------------------------------------------------------------------
/08-Advanced-Hook-Patterns/state-reducer/src/useExpanded.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useRef, useReducer } from 'react'
2 |
3 | const callFunctionsInSequence = (...fns) => (...args) => {
4 | console.log(args)
5 | return fns.forEach(fn => fn && fn(...args))
6 | }
7 |
8 | const internalReducer = (state, action) => {
9 | switch (action.type) {
10 | case useExpanded.types.toggleExpand:
11 | return {
12 | ...state,
13 | expanded: !state.expanded
14 | }
15 | case useExpanded.types.reset:
16 | return {
17 | ...state,
18 | expanded: action.payload
19 | }
20 | case useExpanded.types.override:
21 | return {
22 | ...state,
23 | expanded: !state.expanded
24 | }
25 | default:
26 | throw new Error(`Action type ${action.type} not handled`)
27 | }
28 | }
29 |
30 | export default function useExpanded (
31 | initialExpanded = false,
32 | userReducer = (s, a) => a.internalChanges
33 | ) {
34 | const initialState = { expanded: initialExpanded }
35 | const resolveChangesReducer = (currentInternalState, action) => {
36 | const internalChanges = internalReducer(currentInternalState, action)
37 | const userChanges = userReducer(currentInternalState, {
38 | ...action,
39 | internalChanges
40 | })
41 | return userChanges
42 | }
43 |
44 | const [{ expanded }, setExpanded] = useReducer(
45 | resolveChangesReducer,
46 | initialState
47 | )
48 |
49 | const toggle = useCallback(
50 | () => setExpanded({ type: useExpanded.types.toggleExpand }),
51 | []
52 | )
53 |
54 | const override = useCallback(
55 | () => setExpanded({ type: useExpanded.types.override }),
56 | []
57 | )
58 |
59 | const resetRef = useRef(0)
60 | const reset = useCallback(
61 | () => {
62 | setExpanded({ type: useExpanded.types.reset, payload: initialExpanded })
63 | ++resetRef.current
64 | },
65 | [initialExpanded]
66 | )
67 |
68 | const getTogglerProps = useCallback(
69 | ({ onClick, ...props }) => ({
70 | 'aria-expanded': expanded,
71 | onClick: callFunctionsInSequence(toggle, onClick),
72 | ...props
73 | }),
74 | [toggle, expanded]
75 | )
76 |
77 | const value = useMemo(
78 | () => ({
79 | expanded,
80 | toggle,
81 | getTogglerProps,
82 | reset,
83 | resetDep: resetRef.current,
84 | override
85 | }),
86 | [expanded, toggle, getTogglerProps, reset, override]
87 | )
88 |
89 | return value
90 | }
91 |
92 | useExpanded.types = {
93 | toggleExpand: 'EXPAND',
94 | reset: 'RESET',
95 | override: 'OVERRIDE'
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reintroducing-react
2 |
3 |
4 |
9 |
10 | - 🐛 Lifecycle
11 | - ⚛️ Context
12 | - 🧲 contextType
13 | - 🏬 memo
14 | - 🚀 Profiler
15 | - 🦅 Lazy & Suspense
16 | - 🔌 Hooks
17 | - 💪 Advanced Component Patterns with Hooks
18 | - 🦑 Compound components
19 | - 🕹 Control props
20 | - 🎒 Props collection
21 | - 🛍 Prop getters
22 | - 💖State initializer
23 | - 🦁 State reducer
24 |
25 | ## Useful Links
26 | - Read article on [Medium](https://medium.freecodecamp.org/reintroducing-react-every-react-update-since-v16-demystified-60686ee292cc)
27 | - Download [PDF, Epub & Mobi](https://leanpub.com/reintroducing-react/) absolutely free (or pay whatever you want - if you wanna support my work)
28 |
--------------------------------------------------------------------------------