├── .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 |
    64 | logo 65 |

    You've scored {this.state.points} points.

    66 |
    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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |
    7 | 8 |

    Total Amount

    9 |
    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 | user 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 |
    30 | 31 | 32 |
    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 |
    19 | 20 | 27 | 28 | 29 | 37 | 38 | 39 |
    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 |
    7 | 8 |

    Total Amount

    9 |
    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 | user 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 |
    30 | 31 | 32 |
    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 |
    19 | 20 | 27 | 28 | 29 | 37 | 38 | 39 |
    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 | ![simple medium page](https://i.imgur.com/vYaOyfP.gif) 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 |
    7 | 8 |

    Total Amount

    9 |
    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 | user 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 |
    31 | 32 | 33 |
    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 |
    19 | 20 | 27 | 28 | 29 | 37 | 38 | 39 |
    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 | reintroducing react book cover 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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |
    Awesome Hooks
    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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |
    Awesome Hooks
    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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |
    Terms and Conditions
    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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |

    5 | 6 | react-hooks-cheatsheet 7 | 8 |

    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 | --------------------------------------------------------------------------------