├── .gitattributes ├── .gitignore ├── 00-boilerplate ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 01-use-state ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 02-use-state-object ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 03-component-did-onload ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 04-component_unmount ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 05-component-update-render ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 06-ajax-field-change ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 07-custom-hook ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 08-pure-component ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 09-pure-component-callback ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 10-use-reducer ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 11-use-context ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 12-set-state-func ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 13-async-closure ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 14-use-ref-dom ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 15-promise-unmounted ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 16-memo-predicate ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── src │ ├── app.tsx │ ├── assets │ │ ├── five.png │ │ ├── four.png │ │ ├── one.png │ │ ├── three.png │ │ └── two.png │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 17-use-debug-value ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── resources │ └── 01-dev-tools.png ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── 18-why-did-you-update ├── .babelrc ├── Readme.md ├── Readme_es.md ├── package.json ├── resources │ ├── 01-message-increment-id.gif │ ├── 01-message-increment-id.webm │ ├── 02-message-only-cont.gif │ └── 02-message-only-cont.webm ├── src │ ├── app.tsx │ ├── demo.tsx │ ├── index.html │ ├── index.tsx │ └── styles.css ├── tsconfig.json └── webpack.config.js ├── LICENSE ├── Readme.md └── Readme_es.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub linguist library 2 | 3 | * linguist-vendored 4 | *.js linguist-vendored=false 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist/ 4 | typings/ 5 | *.orig 6 | .idea/ 7 | */src/**/*.js.map 8 | *.log 9 | **/.vscode/ 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /00-boilerplate/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /00-boilerplate/Readme.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/00-boilerplate/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/00-boilerplate/Readme.md) 6 | 7 |
8 |
9 | 10 | 11 | # 00 boilerplate 12 | 13 | ## Resume 14 | 15 | Starting point for the rest of examples. 16 | 17 | It is just a blank project created using create-react-app. 18 | 19 | Hop into [_01-use-state_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/01-use-state) and start with the fun :). 20 | 21 | # About Basefactor + Lemoncode 22 | 23 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 24 | 25 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 26 | 27 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 28 | 29 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 30 | 31 | If you like the world of _backend_ you can sign up to our [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio). 32 | 33 | And if you want to dive into the world _devops_ sign up for our [Bootcamp devops Online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio). 34 | -------------------------------------------------------------------------------- /00-boilerplate/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/00-boilerplate/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/00-boilerplate/Readme.md) 6 | 7 |
8 |
9 | 10 | 11 | # 00 boilerplate 12 | 13 | ## Resumen 14 | 15 | Punto de partida para el resto de ejemplos. 16 | 17 | Es simplemente un proyecto en blanco usando webpack. 18 | 19 | Salta a [_01-use-state_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/01-use-state) y empieza la diversión :). 20 | 21 | # ¿Te apuntas a nuestro máster? 22 | 23 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 24 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 25 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 26 | con clases en vivo, como edición continua con mentorización, para 27 | que puedas ir a tu ritmo y aprender mucho. 28 | 29 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio). 30 | 31 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 32 | apuntate a nuestro [Bootcamp devops Online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio). 33 | -------------------------------------------------------------------------------- /00-boilerplate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /00-boilerplate/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const App = () => { 4 | return

Hello React !!

; 5 | }; 6 | -------------------------------------------------------------------------------- /00-boilerplate/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /00-boilerplate/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /00-boilerplate/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /00-boilerplate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /00-boilerplate/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /01-use-state/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /01-use-state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /01-use-state/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /01-use-state/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyComponent: React.FC = () => { 4 | const [myName, setMyName] = React.useState('John Doe'); 5 | return ( 6 | <> 7 |

{myName}

8 | setMyName(e.target.value)} /> 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /01-use-state/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /01-use-state/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /01-use-state/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /01-use-state/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /01-use-state/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /02-use-state-object/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /02-use-state-object/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /02-use-state-object/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /02-use-state-object/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface UserInfo { 4 | name: string; 5 | lastname: string; 6 | } 7 | 8 | export const MyComponent: React.FC = () => { 9 | const [userInfo, setUserInfo] = React.useState({ 10 | name: "John", 11 | lastname: "Doe", 12 | }); 13 | return ( 14 | <> 15 |

16 | {userInfo.name} {userInfo.lastname} 17 |

18 | 21 | setUserInfo({ 22 | ...userInfo, 23 | name: e.target.value, 24 | }) 25 | } 26 | /> 27 | 30 | setUserInfo({ 31 | ...userInfo, 32 | lastname: e.target.value, 33 | }) 34 | } 35 | /> 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /02-use-state-object/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /02-use-state-object/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /02-use-state-object/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /02-use-state-object/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /02-use-state-object/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /03-component-did-onload/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /03-component-did-onload/Readme.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/03-component-did-onload/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/03-component-did-onload/Readme.md) 6 | 7 |
8 |
9 | 10 | # 03 Component Did Mount 11 | 12 | ## Resume 13 | 14 | This example takes as a starting point the [_02-use-state-object_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/02-use-state-object) example. 15 | 16 | Let's start practicing with another React's core hook: _useEffect_ 17 | 18 | This Hook allows us to subscribe on certain events (check when the 19 | component is mounted, check when the component is unmounted, on 20 | every render, or when a given property is updated). 21 | 22 | Let's start with the most basic, execute a given code when a 23 | component is mounted in the DOM. 24 | 25 | A common scenario: you want to run some code when a component it's loaded into 26 | the DOM, for example loading a list of clients when the component is mounted. 27 | 28 | There can be scenarios when we need some code to be executed when a given 29 | property value changes or right after each render. 30 | 31 | There may be scenarios when all this operations are not synchronous? For instance I want making a call to a server rest api, 32 | this will return a promise, it is not safe at all to run this directly in a functional component 33 | since the functional component is executed and destroyed, to manage these side effects) we can make use of 34 | _React.useEffect_ 35 | 36 | In this example we are going to simulate a call to a rest api, in order to retrieve a name (we will use 37 | _setTimeout_). 38 | 39 | ## Steps 40 | 41 | First we copy the previous example, and do a _npm install_ 42 | 43 | ```bash 44 | npm install 45 | ``` 46 | 47 | - Let's open the _demo.tsx_ file, and overwrite it with the following content. 48 | 49 | _./src/demo.tsx_ 50 | 51 | ```jsx 52 | import React from "react"; 53 | 54 | export const MyComponent = () => { 55 | const [username, setUsername] = React.useState(""); 56 | 57 | return ( 58 | <> 59 |

{username}

60 | setUsername(e.target.value)} /> 61 | 62 | ); 63 | }; 64 | ``` 65 | 66 | - If we run the example, the name field will be empty, but we want 67 | to assign some value right when the component is mounted? We can make use of 68 | _React.useEffect_ passing as a second argument an empty array (that's important 69 | if we don't pass this the code inside the _useEffect_ would be executed on 70 | mount and after every render). 71 | 72 | _./src/demo.js_ 73 | 74 | ```diff 75 | import React from "react"; 76 | 77 | export const MyComponent = () => { 78 | const [username, setUsername] = React.useState(""); 79 | 80 | + React.useEffect(() => { 81 | + setUsername("John"); 82 | + }, []); 83 | 84 | return ( 85 | <> 86 |

{username}

87 | setUsername(e.target.value)} /> 88 | 89 | ); 90 | }; 91 | ``` 92 | 93 | - Now if you run the sample you can check that _John_ is displayed as user name. 94 | 95 | * Let's go one step further, let's simulate an asynchronous call (we will do it 96 | using _setTimeout_). 97 | 98 | _./src/demo.js_ 99 | 100 | ```diff 101 | import React from "react"; 102 | 103 | export const MyComponent = () => { 104 | const [username, setUsername] = React.useState(""); 105 | 106 | React.useEffect(() => { 107 | - setUsername("John"); 108 | + // Simulating async call 109 | + setTimeout(() => { 110 | + setUsername("John"); 111 | + }, 1500); 112 | }, []); 113 | 114 | return ( 115 | <> 116 |

{username}

117 | setUsername(e.target.value)} /> 118 | 119 | ); 120 | }; 121 | ``` 122 | 123 | - Now _John_ is displayed after 1,5 seconds, instead of _setTimeout_ you could 124 | use here _fetch_ or any other similar approach to make an ajax request. 125 | 126 | # About Basefactor + Lemoncode 127 | 128 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 129 | 130 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 131 | 132 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 133 | 134 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 135 | -------------------------------------------------------------------------------- /03-component-did-onload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /03-component-did-onload/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /03-component-did-onload/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyComponent = () => { 4 | const [username, setUsername] = React.useState(""); 5 | 6 | React.useEffect(() => { 7 | // Simulating async call 8 | setTimeout(() => { 9 | setUsername("John"); 10 | }, 1500); 11 | }, []); 12 | 13 | return ( 14 | <> 15 |

{username}

16 | setUsername(e.target.value)} /> 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /03-component-did-onload/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /03-component-did-onload/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /03-component-did-onload/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /03-component-did-onload/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /03-component-did-onload/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /04-component_unmount/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /04-component_unmount/Readme.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/04-component_unmount/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/04-component_unmount/Readme.md) 6 | 7 |
8 |
9 | 10 | # 04 Component unmount 11 | 12 | ## Resume 13 | 14 | This example takes the [_03-component-dom-onload_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/03-component-did-onload) example as starting point. 15 | 16 | In this example we are going to see how to free resources when we unmount a DOM component. 17 | 18 | # Steps 19 | 20 | - First we copy the previous example, and execute _npm install_. 21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | - We are going to create a component that shows or hides a text depending on a boolean flag. 27 | 28 | Overwrite _demo.js_ file with the following content. 29 | 30 | _./src/demo.js_ 31 | 32 | ```jsx 33 | import React from "react"; 34 | 35 | export const MyComponent = () => { 36 | const [visible, setVisible] = React.useState(false); 37 | 38 | return ( 39 | <> 40 | {visible && } 41 | 44 | 45 | ); 46 | }; 47 | ``` 48 | 49 | At first the text is not displayed because _visible_ is false. 50 | 51 | We are going to add a button to change the state of _visible_ 52 | 53 | _./src/demo.tsx_ 54 | 55 | ```diff 56 | return ( 57 | <> 58 | {visible &&

Hello

} 59 | + 62 | 63 | ); 64 | ``` 65 | 66 | Let's replace the basic element \ _h4 \ _ \ \_, and let's define a child component: 67 | 68 | ```diff 69 | + export const MyChildComponent = () => { 70 | + return

Hello form child component

71 | + } 72 | 73 | export const MyComponent = () => { 74 | const [visible, setVisible] = React.useState(false); 75 | 76 | return ( 77 | <> 78 | - {visible &&

Hello

} 79 | + {visible && } 80 | 83 | 84 | ); 85 | }; 86 | ``` 87 | 88 | - Now we got a childr component that is mounted / unmounted from the dom when a user clicks on a button. 89 | 90 | How could we do to display a message on the console 91 | browser when the child component will be mounted? 92 | If we remember the previous example, we can use _React.useEffect_. 93 | Before continue just give a try by yourself. 94 | 95 | We could do something like: 96 | 97 | _./src/demo.tsx_ 98 | 99 | ```diff 100 | export const MyChildComponent = () => { 101 | + React.useEffect(() => { 102 | + console.log('Component just mounted on the DOM') 103 | + }, []) 104 | + 105 | return

Hello form child component

; 106 | }; 107 | ``` 108 | 109 | - Now comes the interesting part, what if we want to show a message on the browser console when the component is unmounted from the DOM? Just 110 | by adding in the return value of that _useEffect_ a function that will be called when the component is unmounted. 111 | 112 | _./src/demo.js_ 113 | 114 | ```diff 115 | export const MyChildComponent = () => { 116 | React.useEffect(() => { 117 | console.log("El componente se acaba de montar en el DOM"); 118 | + return () => console.log("El componente se acaba de desmontar del DOM"); 119 | }, []); 120 | ``` 121 | 122 | Any useful scenarios? Imagine that you open a connection to a websocket and you want to close it when the user hides the component, how do you free resources in a proper way? By using this approach. 123 | 124 | # About Basefactor + Lemoncode 125 | 126 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 127 | 128 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 129 | 130 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 131 | 132 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 133 | -------------------------------------------------------------------------------- /04-component_unmount/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/04-component_unmount/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/04-component_unmount/Readme.md) 6 | 7 |
8 |
9 | 10 | # 04 Component DOM unmount 11 | 12 | ## Resumen 13 | 14 | Este ejemplo toma como punto de partida el ejemplo [_03-component-dom-onload_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/03-component-did-onload). 15 | 16 | En este ejemplo vamos a ver como liberar recursos cuando desmontamos un 17 | componente del DOM. 18 | 19 | ## Paso a Paso 20 | 21 | - Primero copiamos el ejemplo anterior, y hacemos un _npm install_ 22 | 23 | ```bash 24 | npm install 25 | ``` 26 | 27 | - Vamos a crear un componente que muestra o oculta un texto dependiendo 28 | de un flag booleano. 29 | 30 | _./src/demo.tsx_ 31 | 32 | ```tsx 33 | import React from "react"; 34 | 35 | export const MyComponent = () => { 36 | const [visible, setVisible] = React.useState(false); 37 | 38 | return <>{visible &&

Hello

}; 39 | }; 40 | ``` 41 | 42 | De primeras no se muestra el texto porque _visible_ está a falso. 43 | 44 | Vamos a añadir un botón para cambiar el estado de _visible_ 45 | 46 | _./src/demo.tsx_ 47 | 48 | ```diff 49 | return ( 50 | <> 51 | {visible &&

Hello

} 52 | + 55 | 56 | ); 57 | ``` 58 | 59 | - Y si en vez de un \_h4\_\_, instanciamos un componente: 60 | 61 | ```diff 62 | + export const MyChildComponent = () => { 63 | + return

Hello form child component

64 | + } 65 | 66 | export const MyComponent = () => { 67 | const [visible, setVisible] = React.useState(false); 68 | 69 | return ( 70 | <> 71 | - {visible &&

Hello

} 72 | + {visible && } 73 | 76 | 77 | ); 78 | }; 79 | ``` 80 | 81 | - Ahora tenemos un componente hijo que pinchando en un botón 82 | se monta o desmonta del dom. 83 | 84 | ¿Cómo podríamos hacer para mostrar un mensaje por la consola 85 | del navegador cuando se montara el componente hijo? 86 | Si recordamos el ejemplo anterior,sería con _React.useEffect_ 87 | ¿Te animas a intentarlo? Dale a la pausa al video y ponte :). 88 | 89 | Podríamos hacer algo así como: 90 | 91 | _./src/demo.tsx_ 92 | 93 | ```diff 94 | export const MyChildComponent = () => { 95 | + React.useEffect(() => { 96 | + console.log('El componente se acaba de montar en el DOM') 97 | + }, []) 98 | + 99 | return

Hello form child component

; 100 | }; 101 | ``` 102 | 103 | - Ahora viene la parte interesante, y si queremos mostrar un mensaje 104 | por la console del navegador cuando el componente se desmonta del DOM? 105 | En la misma función que ponemos como primer parametro devolvemos 106 | la función de "limpieza"... _useEffect_ se va a guardar esa función 107 | hasta que desmonte el DOM para invocarla: 108 | 109 | ```diff 110 | export const MyChildComponent = () => { 111 | React.useEffect(() => { 112 | console.log("El componente se acaba de montar en el DOM"); 113 | + return () => console.log("El componente se acaba de desmontar del DOM"); 114 | }, []); 115 | ``` 116 | 117 | ¿Para que me puede servir esto? Imaginate que abres una conexión a un websocket 118 | y quieres cerrarla cuando el usuario oculte el componente, ¿ cómo liberas 119 | recursos de forma ordenada? Aquí lo tienes. 120 | 121 | # ¿Te apuntas a nuestro máster? 122 | 123 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 124 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 125 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 126 | con clases en vivo, como edición continua con mentorización, para 127 | que puedas ir a tu ritmo y aprender mucho. 128 | 129 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 130 | 131 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 132 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 133 | -------------------------------------------------------------------------------- /04-component_unmount/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /04-component_unmount/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /04-component_unmount/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyChildComponent = () => { 4 | return

Hello from Child Component

; 5 | }; 6 | 7 | export const MyComponent = () => { 8 | const [visible, setVisible] = React.useState(false); 9 | 10 | React.useEffect(() => { 11 | console.log("El componente se acaba de montar en el DOM"); 12 | 13 | return () => { 14 | console.log("El componente se acaba de desmontar del DOM"); 15 | }; 16 | }, []); 17 | 18 | return ( 19 | <> 20 | {visible && } 21 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /04-component_unmount/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /04-component_unmount/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /04-component_unmount/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /04-component_unmount/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /04-component_unmount/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /05-component-update-render/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /05-component-update-render/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | [español](https://lemoncode.net/) 3 | 4 | 5 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/05-component-update-render/Readme_es.md) 6 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/05-component-update-render/Readme.md) 7 | 8 |
9 |
10 | 11 | # 05 Component update render 12 | 13 | ## Resume 14 | 15 | We will take as starting point sample [_04-component-dom-unmount_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/04-component_unmount). 16 | 17 | In this example we will check how to use React.useEffect in order to execute a given code right after each render. 18 | 19 | # Steps 20 | 21 | - First we copy the previous example, and we execute _npm install_ 22 | 23 | ```bash 24 | npm install 25 | ``` 26 | 27 | - Let's open the _demo.tsx_, we will create a parent and child component as 28 | we did in previous examples. 29 | 30 | _./src/demo.tsx_ 31 | 32 | ```tsx 33 | import React from "react"; 34 | 35 | export const MyComponent = () => { 36 | const [visible, setVisible] = React.useState(false); 37 | 38 | return ( 39 | <> 40 | {visible && } 41 | 44 | 45 | ); 46 | }; 47 | 48 | const MyChildComponent = () => { 49 | const [userInfo, setUserInfo] = React.useState({ 50 | name: "John", 51 | lastname: "Doe", 52 | }); 53 | 54 | return ( 55 |
56 |

57 | {userInfo.name} {userInfo.lastname} 58 |

59 | setUserInfo({ ...userInfo, name: e.target.value })} 62 | /> 63 | setUserInfo({ ...userInfo, lastname: e.target.value })} 66 | /> 67 |
68 | ); 69 | }; 70 | ``` 71 | 72 | - Now comes the interesting part: by calling _React.useEffect_ without a second 73 | parameter, the code inside _useEffect_ will be triggered right when the 74 | component is just mounted and on any update (clean up function will be called 75 | right before the effect is triggered again). 76 | 77 | _./src/demo.js_ 78 | 79 | ```diff 80 | const MyChildComponent = () => { 81 | const [userInfo, setUserInfo] = React.useState({ 82 | name: "John", 83 | lastname: "Doe" 84 | }); 85 | 86 | + React.useEffect(() => { 87 | + console.log("A. Called when the component is mounted and after every render"); 88 | + 89 | + ); 90 | + }); 91 | 92 | return ( 93 | ``` 94 | 95 | - If we execute this, we can check that this code is executed after each render of the component. 96 | 97 | - We can also add a function to free up resources just before the next render is executed. 98 | 99 | ```diff 100 | React.useEffect(() => { 101 | console.log("A. Called when the component is mounted and after every render"); 102 | 103 | + return () => 104 | + console.log( 105 | + "B. Cleanup function called after every render" 106 | + ); 107 | + }); 108 | ``` 109 | 110 | - If we start the project and open the browser console we can check the 111 | expected behavior. 112 | 113 | # About Basefactor + Lemoncode 114 | 115 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 116 | 117 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 118 | 119 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 120 | 121 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 122 | -------------------------------------------------------------------------------- /05-component-update-render/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/05-component-update-render/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/05-component-update-render/Readme.md) 6 | 7 |
8 |
9 | 10 | # 05 Component update render 11 | 12 | ## Resumen 13 | 14 | Este ejemplo toma como punto de partida el ejemplo [_04-component-dom-unmount_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/04-component_unmount). 15 | 16 | En este ejemplo vamos a ver como usar React.useEffect para ejecutar 17 | un código justo después de cada renderizado. 18 | 19 | ## Paso a Paso 20 | 21 | - Primero copiamos el ejemplo anterior, y hacemos un _npm install_ 22 | 23 | ```bash 24 | npm install 25 | ``` 26 | 27 | - Vamos abrir el fichero _demo.js_ y crear el ejemplo de un componente 28 | padre y un hijo que se muestra dependiendo de una condición booleana. 29 | 30 | ```tsx 31 | import React from "react"; 32 | 33 | export const MyComponent = () => { 34 | const [visible, setVisible] = React.useState(false); 35 | 36 | return ( 37 | <> 38 | {visible && } 39 | 42 | 43 | ); 44 | }; 45 | 46 | const MyChildComponent = () => { 47 | const [userInfo, setUserInfo] = React.useState({ 48 | name: "John", 49 | lastname: "Doe", 50 | }); 51 | 52 | return ( 53 |
54 |

55 | {userInfo.name} {userInfo.lastname} 56 |

57 | setUserInfo({ ...userInfo, name: e.target.value })} 60 | /> 61 | setUserInfo({ ...userInfo, lastname: e.target.value })} 64 | /> 65 |
66 | ); 67 | }; 68 | ``` 69 | 70 | - Ahora viene la parte interesante, vamos a llamar a _React.useEffect_ sólo 71 | informando el primer parametro. 72 | 73 | ```diff 74 | const MyChildComponent = () => { 75 | const [userInfo, setUserInfo] = React.useState({ 76 | name: "John", 77 | lastname: "Doe" 78 | }); 79 | 80 | + React.useEffect(() => { 81 | + console.log("A. Called right after every render"); 82 | + 83 | + }); 84 | 85 | return ( 86 | ``` 87 | 88 | - Si ejecutamos podemos ver que este código se ejecuta después de cada renderizado del componente. 89 | 90 | - También podemos añadir una función para liberar recursos justo antes de que se ejecute el siguiente render. 91 | 92 | ```diff 93 | React.useEffect(() => { 94 | console.log("A. Called when the component is mounted and after every render"); 95 | 96 | + return () => 97 | + console.log( 98 | + "B. Cleanup function called after every render" 99 | + ); 100 | + }); 101 | ``` 102 | 103 | - Si ejecutamos podemos ver como se invocan las dos funciones. 104 | 105 | ```bash 106 | npm start 107 | ``` 108 | 109 | # ¿Te apuntas a nuestro máster? 110 | 111 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 112 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 113 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 114 | con clases en vivo, como edición continua con mentorización, para 115 | que puedas ir a tu ritmo y aprender mucho. 116 | 117 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 118 | 119 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 120 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 121 | -------------------------------------------------------------------------------- /05-component-update-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /05-component-update-render/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /05-component-update-render/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyComponent = () => { 4 | const [visible, setVisible] = React.useState(false); 5 | 6 | return ( 7 | <> 8 | {visible && } 9 | 12 | 13 | ); 14 | }; 15 | 16 | const MyChildComponent = () => { 17 | const [userInfo, setUserInfo] = React.useState({ 18 | name: "John", 19 | lastname: "Doe", 20 | }); 21 | 22 | React.useEffect(() => { 23 | console.log("A. Called right after every render"); 24 | 25 | return () => console.log("B. Cleanup function called after every render"); 26 | }); 27 | 28 | return ( 29 |
30 |

31 | {userInfo.name} {userInfo.lastname} 32 |

33 | setUserInfo({ ...userInfo, name: e.target.value })} 36 | /> 37 | setUserInfo({ ...userInfo, lastname: e.target.value })} 40 | /> 41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /05-component-update-render/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /05-component-update-render/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /05-component-update-render/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /05-component-update-render/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /05-component-update-render/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /06-ajax-field-change/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /06-ajax-field-change/Readme.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/06-ajax-field-change/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/06-ajax-field-change/Readme.md) 6 | 7 |
8 |
9 | 10 | # 06 Ajax field change 11 | 12 | ## Resume 13 | 14 | This example takes as a starting point the example [_05-component-update-render_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/05-component-update-render). 15 | 16 | Let's simulate a real scenario, we have a search result list(we read this from a server source), and each time that we enter a change to a filtering input we want to send a request to the server to get the new filtered list and display it. 17 | 18 | As a bonus, we will check how to use Debounce (wait a little until the user stops typing to send the request, saving so unnecessary calls). 19 | 20 | # Steps 21 | 22 | - First we copy the previous example, and do a _npm install_. 23 | 24 | ```bash 25 | npm install 26 | ``` 27 | 28 | - Let's open the _demo.js_, and let's add an entry in the state that stores the current search filter, and another state in which we 29 | are going to store a list of users. 30 | 31 | _./src/demo.tsx_ 32 | 33 | ```tsx 34 | import React from "react"; 35 | 36 | export const MyComponent = () => { 37 | const [filter, setFilter] = React.useState(""); 38 | const [userCollection, setUserCollection] = React.useState([]); 39 | 40 | return ( 41 |
42 | setFilter(e.target.value)} /> 43 |
    44 | {userCollection.map((user, index) => ( 45 |
  • {user.name}
  • 46 | ))} 47 |
48 |
49 | ); 50 | }; 51 | ``` 52 | 53 | - Now we want to fire an _ajax request_ every time the user types on the filter input. 54 | 55 | _./src/demo.tsx_ 56 | 57 | ```diff 58 | export const MyComponent = () => { 59 | const [filter, setFilter] = React.useState(''); 60 | const [userCollection, setUserCollection] = React.useState([]); 61 | 62 | + // Load full list when the component gets mounted and filter gets updated 63 | + React.useEffect(() => { 64 | + fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`) 65 | + .then(response => response.json()) 66 | + .then(json => setUserCollection(json)); 67 | + }, [filter]); 68 | 69 | return ( 70 | ``` 71 | 72 | **BE CAREFUL!!! Typicode** since we are hitting a free rest api it maybe down or asleep, maybe you will have to give some tries :). 73 | 74 | You can try as with other apis as well (you will need to take a look at the documention, some of them return slightly different 75 | response structures). 76 | 77 | https://rickandmortyapi.com/ 78 | 79 | https://swapi.dev/documentation#auth 80 | 81 | ```tsx 82 | React.useEffect(() => { 83 | fetch(`https://swapi.dev/api/people?search=${filter}`) 84 | .then((response) => response.json()) 85 | .then((json) => setUserCollection(json.results)); 86 | }, [filter]); 87 | ``` 88 | 89 | - If we execute this code we can see that the filtering option works. 90 | 91 | ```bash 92 | npm start 93 | ``` 94 | 95 | ## BONUS 96 | 97 | This is fine, but it isn't optimal, we usually want to trigger the search just when the user has stopped typing to avoid making unnecessary calls. 98 | 99 | We can download a library that implements a custom hook that just implements that behavior: https://github.com/xnimorz/use-debounce 100 | 101 | Using this is a piece of cake: 102 | 103 | ```bash 104 | npm install use-debounce --save 105 | ``` 106 | 107 | ```diff 108 | + import { useDebounce } from 'use-debounce'; 109 | 110 | export const MyComponent = () => { 111 | const [filter, setFilter] = React.useState(""); 112 | + const [debouncedFilter] = useDebounce(filter, 500); 113 | const [userCollection, setUserCollection] = React.useState([]); 114 | 115 | // Load full list when the component gets mounted and filter gets updated 116 | React.useEffect(() => { 117 | fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`) 118 | .then((response) => response.json()) 119 | .then((json) => setUserCollection(json)); 120 | - }, [filter]); 121 | + }, [debouncedFilter]); 122 | ``` 123 | 124 | # About Basefactor + Lemoncode 125 | 126 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 127 | 128 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 129 | 130 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 131 | 132 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 133 | -------------------------------------------------------------------------------- /06-ajax-field-change/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/06-ajax-field-change/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/06-ajax-field-change/Readme.md) 6 | 7 |
8 |
9 | 10 | # 06 AJAX Field Change 11 | 12 | ## Resumen 13 | 14 | Este ejemplo toma como punto de partida el ejemplo [_05-component-update-render_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/05-component-update-render). 15 | 16 | Pasamos a ver un ejemplo práctico, tenemos un listado de resultado de busqueda 17 | (esto viene de un servidor), y queremos que cada vez que introduzcamos un 18 | cambio en un input de filtrado, envíe una petición a servidor para obtener 19 | la nueva lista filtrada y mostrarla. 20 | 21 | Como postre veremos como utilizar Debounce (es decir esperar un poquito a 22 | que el usuario termine de teclear para enviar la petición, ahorrando 23 | así llamadas innecesarias). 24 | 25 | ## Paso a Paso 26 | 27 | - Primero copiamos el ejemplo anterior, y hacemos un _npm install_. 28 | 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | - Vamos abrir el fichero _demo.js_ y vamos añadir una entrada en el 34 | estado que almacene el filtro actual de busqueda, y otra en la que almacene 35 | una lista de usuarios. 36 | 37 | _./src/demo.tsx_ 38 | 39 | ```tsx 40 | import React from "react"; 41 | 42 | export const MyComponent = () => { 43 | const [filter, setFilter] = React.useState(""); 44 | const [userCollection, setUserCollection] = React.useState([]); 45 | 46 | return ( 47 |
48 | setFilter(e.target.value)} /> 49 |
    50 | {userCollection.map((user, index) => ( 51 |
  • {user.name}
  • 52 | ))} 53 |
54 |
55 | ); 56 | }; 57 | ``` 58 | 59 | - Ahora vamos a acceder a un rest api json, _typicode_ nos da alguna gratuita, 60 | esta por ejemplo: _https://jsonplaceholder.typicode.com/users_ además permite 61 | aplicar filtrados, la ponemos en el useEffecy y le añadimos como filtro que 62 | se ejecute cada vez que se cambie el filtro de busqueda: 63 | 64 | ```diff 65 | export const MyComponent = () => { 66 | const [filter, setFilter] = React.useState(''); 67 | const [userCollection, setUserCollection] = React.useState([]); 68 | 69 | + // Load full list when the component gets mounted and filter gets updated 70 | + React.useEffect(() => { 71 | + fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`) 72 | + .then(response => response.json()) 73 | + .then(json => setUserCollection(json)); 74 | + }, [filter]); 75 | 76 | return ( 77 | ``` 78 | 79 | **OJO !!! Typicode** corre en un heroku gratuito que se duerme cada X tiempo :) 80 | Vamos a probar con otras API. 81 | 82 | Ojo, que esto impactara en el codigo, tenemos que meter algún cambio y 83 | ver que devuelven estas api, esto lo haremos como ejercicio. 84 | 85 | https://rickandmortyapi.com/ 86 | 87 | https://swapi.dev/documentation#auth 88 | 89 | ```tsx 90 | React.useEffect(() => { 91 | fetch(`https://swapi.dev/api/people?search=${filter}`) 92 | .then((response) => response.json()) 93 | .then((json) => setUserCollection(json.results)); 94 | }, [filter]); 95 | ``` 96 | 97 | - Si ejecutamos este código podemos ver que la opcíon de filtrado funciona. 98 | 99 | ```bash 100 | npm start 101 | ``` 102 | 103 | ## BONUS 104 | 105 | Esto está bien, pero no es optimo, normalmente queremos disparar la busqueda 106 | justo cuando el usuario ha dejado de teclear para evitar hacer llamadas 107 | innecesarias. 108 | 109 | Nos podemos bajar una librería que implement un custom hook que hace 110 | justo eso: https://github.com/xnimorz/use-debounce 111 | 112 | Lo único que tendríamos que hacer: 113 | 114 | ```bash 115 | npm install use-debounce --save 116 | ``` 117 | 118 | ```diff 119 | + import { useDebounce } from 'use-debounce'; 120 | 121 | export const MyComponent = () => { 122 | const [filter, setFilter] = React.useState(""); 123 | + const [debouncedFilter] = useDebounce(filter, 500); 124 | const [userCollection, setUserCollection] = React.useState([]); 125 | 126 | // Load full list when the component gets mounted and filter gets updated 127 | React.useEffect(() => { 128 | fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`) 129 | .then((response) => response.json()) 130 | .then((json) => setUserCollection(json)); 131 | - }, [filter]); 132 | + }, [debouncedFilter]); 133 | ``` 134 | 135 | # ¿Te apuntas a nuestro máster? 136 | 137 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 138 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 139 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 140 | con clases en vivo, como edición continua con mentorización, para 141 | que puedas ir a tu ritmo y aprender mucho. 142 | 143 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 144 | 145 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 146 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 147 | -------------------------------------------------------------------------------- /06-ajax-field-change/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "use-debounce": "^7.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /06-ajax-field-change/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /06-ajax-field-change/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDebounce } from 'use-debounce'; 3 | 4 | export const MyComponent = () => { 5 | const [filter, setFilter] = React.useState(""); 6 | const [debouncedFilter] = useDebounce(filter, 500); 7 | const [userCollection, setUserCollection] = React.useState([]); 8 | 9 | // Load full list when the component gets mounted and filter gets updated 10 | React.useEffect(() => { 11 | fetch(`https://swapi.dev/api/people?search=${filter}`) 12 | .then((response) => response.json()) 13 | .then((json) => setUserCollection(json.results)); 14 | }, [debouncedFilter]); 15 | 16 | return ( 17 |
18 | setFilter(e.target.value)} /> 19 |
    20 | {userCollection.map((user, index) => ( 21 |
  • {user.name}
  • 22 | ))} 23 |
24 |
25 | ); 26 | }; -------------------------------------------------------------------------------- /06-ajax-field-change/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /06-ajax-field-change/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /06-ajax-field-change/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /06-ajax-field-change/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /06-ajax-field-change/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /07-custom-hook/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /07-custom-hook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "use-debounce": "^7.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /07-custom-hook/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /07-custom-hook/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const useUserCollection = () => { 4 | const [filter, setFilter] = React.useState(""); 5 | const [userCollection, setUserCollection] = React.useState([]); 6 | 7 | const loadUsers = () => { 8 | fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`) 9 | .then((response) => response.json()) 10 | .then((json) => setUserCollection(json)); 11 | }; 12 | 13 | return { userCollection, loadUsers, filter, setFilter }; 14 | }; 15 | 16 | export const MyComponent = () => { 17 | const { userCollection, loadUsers, filter, setFilter } = useUserCollection(); 18 | 19 | React.useEffect(() => { 20 | loadUsers(); 21 | }, [filter]); 22 | 23 | return ( 24 |
25 | setFilter(e.target.value)} /> 26 |
    27 | {userCollection.map((user, index) => ( 28 |
  • {user.name}
  • 29 | ))} 30 |
31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /07-custom-hook/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /07-custom-hook/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /07-custom-hook/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /07-custom-hook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /07-custom-hook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /08-pure-component/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /08-pure-component/Readme.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component/Readme.md) 6 | 7 |
8 |
9 | 10 | # 08 Pure Components 11 | 12 | When we used class components we could make use of PureComponents, these 13 | components will just make a shallow compare of the props and only render 14 | if there were changes. Is there a way to do this using hooks? Yes, 15 | using _React.memo_ 16 | 17 | # Steps 18 | 19 | - We will take as starting point sample [_07-custom-hook_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/07-custom-hook). Let's copy the content of the 20 | project into a fresh folder an execute _npm install_. 21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | - Let's open the _demo.tsx_, we will create a parent and a child component 27 | 28 | _./src/demo.tsx_ 29 | 30 | ```tsx 31 | import React from "react"; 32 | 33 | interface Props { 34 | name: string; 35 | } 36 | 37 | export const DisplayUsername = (props: Props) => { 38 | console.log( 39 | "Hey I'm only rerendered when name gets updated, check React.memo" 40 | ); 41 | 42 | return

{props.name}

; 43 | }; 44 | 45 | export const MyComponent = () => { 46 | const [userInfo, setUserInfo] = React.useState({ 47 | name: " John ", 48 | lastname: "Doe", 49 | }); 50 | 51 | return ( 52 | <> 53 | 54 | 57 | setUserInfo({ 58 | ...userInfo, 59 | name: e.target.value, 60 | }) 61 | } 62 | /> 63 | 66 | setUserInfo({ 67 | ...userInfo, 68 | lastname: e.target.value, 69 | }) 70 | } 71 | /> 72 | 73 | ); 74 | }; 75 | ``` 76 | 77 | - If we run the sample we can check that any time we change for instance 78 | _lastname_ it will rerender _DisplayUsername_, the optimal approach 79 | could be only to rerender _DisplayUsername_ just when the _props.name_ 80 | is updated, if we wrap the _DisplayUsername_ component using _React.memo_ 81 | it will do the trick for us. 82 | 83 | _./src/demo.tsx_ 84 | 85 | ```diff 86 | - export const DisplayUsername = props => { 87 | + export const DisplayUsername = React.memo(props => { 88 | 89 | console.log( 90 | "Hey I'm only rerendered when name gets updated, check React.memo" 91 | ); 92 | 93 | return

{props.name}

; 94 | - }; 95 | + }); 96 | ``` 97 | 98 | - Now if we run the sample we can check (by showing the console or open react dev 99 | tools) that the _DisplayUsername_ component is only rerendered when the _name_ property 100 | changes. 101 | 102 | # About Basefactor + Lemoncode 103 | 104 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 105 | 106 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 107 | 108 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 109 | 110 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 111 | 112 | -------------------------------------------------------------------------------- /08-pure-component/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component/Readme.md) 6 | 7 |
8 |
9 | 10 | # 08 Pure component 11 | 12 | ## Resumen 13 | 14 | Este ejemplo toma como punto de partida el ejemplo [_07-custom-hook_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/07-custom-hook). 15 | 16 | Hay veces en las que nos hace falta hilar fino y sólo volver a repintar un componente si sus propiedades han cambiado, si trabajamos con estructuras inmutables sólo tenemos que hacer un shallow compare. 17 | 18 | Esto lo podíamos hacer facilmente con componentes de clase ¿ Cómo podemos hacerlo en componente de tipo función? 19 | Vamos a ello. 20 | 21 | ## Paso a Paso 22 | 23 | - Primero copiamos el ejemplo anterior, y hacemos un _npm install_ 24 | 25 | ```bash 26 | npm install 27 | ``` 28 | 29 | - Vamos a pegar un ejemplo en _demo.js_, este código va tener dos 30 | valores editables: _name_ y _lastname_ y vamos a tener un control 31 | hijo que sólo va a mostrar el _name_ (de hecho este componente 32 | sólo pedirá el campo nombre en las propiedades). 33 | 34 | _./src_/demo.tsx\_ 35 | 36 | ```tsx 37 | import React from "react"; 38 | 39 | interface Props { 40 | name: string; 41 | } 42 | 43 | export const DisplayUsername = (props: Props) => { 44 | console.log( 45 | "Hey I'm only rerendered when name gets updated, check React.memo" 46 | ); 47 | 48 | return

{props.name}

; 49 | }; 50 | 51 | export const MyComponent = () => { 52 | const [userInfo, setUserInfo] = React.useState({ 53 | name: " John ", 54 | lastname: "Doe", 55 | }); 56 | 57 | return ( 58 | <> 59 | 60 | 63 | setUserInfo({ 64 | ...userInfo, 65 | name: e.target.value, 66 | }) 67 | } 68 | /> 69 | 72 | setUserInfo({ 73 | ...userInfo, 74 | lastname: e.target.value, 75 | }) 76 | } 77 | /> 78 | 79 | ); 80 | }; 81 | ``` 82 | 83 | - Para ver cuando se repinta hemos metido un _console.log_ en el componente hijo (DisplayUsername). 84 | 85 | - Si lanzamos el ejemplo y probamos, veremos que da igual si cambio nombre o apellido el componente 86 | se repinta, y sólo queremos que se repinte cuando cambie el campo nombre, ¿Qué podemos hacer? 87 | **React.memo** al rescate, vamos a envolver el componente: 88 | 89 | _./src/demo.tsx_ 90 | 91 | ```diff 92 | - export const DisplayUsername = (props: Props) => { 93 | + export const DisplayUsername = React.memo((props: Props) => { 94 | 95 | console.log( 96 | "Hey I'm only rerendered when name gets updated, check React.memo" 97 | ); 98 | 99 | return

{props.name}

; 100 | - }; 101 | + }); 102 | ``` 103 | 104 | - Si ejecutamos el ejemplo, podemos ver que ahora sólo se repinta el componente cuando 105 | cambia la propiedad nombre: 106 | 107 | ```bash 108 | npm start 109 | ``` 110 | 111 | ¿ Qué es lo que está haciendo _React.memo_? Esta aplicando el patrón memorize, recuerda 112 | para la propiedad name el puntero del último render, cuando llega el siguiente los compara 113 | y si son iguales devuelve el render del componente cacheado. 114 | 115 | # ¿Te apuntas a nuestro máster? 116 | 117 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 118 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 119 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 120 | con clases en vivo, como edición continua con mentorización, para 121 | que puedas ir a tu ritmo y aprender mucho. 122 | 123 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 124 | 125 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 126 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 127 | -------------------------------------------------------------------------------- /08-pure-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "use-debounce": "^7.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /08-pure-component/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /08-pure-component/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface Props { 4 | name: string; 5 | } 6 | 7 | export const DisplayUsername = React.memo((props: Props) => { 8 | console.log( 9 | "Hey I'm only rerendered when name gets updated, check React.memo" 10 | ); 11 | 12 | return

{props.name}

; 13 | }); 14 | 15 | export const MyComponent = () => { 16 | const [userInfo, setUserInfo] = React.useState({ 17 | name: " John ", 18 | lastname: "Doe", 19 | }); 20 | 21 | return ( 22 | <> 23 | 24 | 27 | setUserInfo({ 28 | ...userInfo, 29 | name: e.target.value, 30 | }) 31 | } 32 | /> 33 | 36 | setUserInfo({ 37 | ...userInfo, 38 | lastname: e.target.value, 39 | }) 40 | } 41 | /> 42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /08-pure-component/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /08-pure-component/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /08-pure-component/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /08-pure-component/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /08-pure-component/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /09-pure-component-callback/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /09-pure-component-callback/Readme.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/09-pure-component-callback/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/09-pure-component-callback/Readme.md) 6 | 7 |
8 |
9 | 10 | # 09 Pure Components Callback 11 | 12 | In the previous sample we saw how to make a component pure using 13 | _React.memo_, that's great, but when there's an issue 14 | what happens if we pass a function created inside the functional component to the child component? 15 | That function will be always different on every render thus 16 | the _memo_ won't take effect. 17 | 18 | How can we solve this? We can make use of _useCallback_, this won't mutate the setter 19 | function unless we indicate any dependency (same approach as with _React.useEffect_). 20 | 21 | # Steps 22 | 23 | - We will take as starting point sample [_08-pure-component_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component). Copy the content of the 24 | project to a fresh folder an execute _npm install_. 25 | 26 | ```bash 27 | npm install 28 | ``` 29 | 30 | - Let's open the _demo.tsx_ file. We will create a parent and a child component 31 | (this time the child component will just reset the name content). 32 | 33 | _./src/demo.tsx_ 34 | 35 | ```tsx 36 | import React from "react"; 37 | 38 | interface Props { 39 | onReset: () => void; 40 | } 41 | 42 | const ResetValue: React.FC = React.memo((props) => { 43 | console.log( 44 | "Hey I'm only rendered the first time, check React.memo + callback" 45 | ); 46 | 47 | return ; 48 | }); 49 | 50 | export const MyComponent = () => { 51 | const [username, setUsername] = React.useState("John"); 52 | const [lastname, setLastname] = React.useState("Doe"); 53 | 54 | const resetNameCallback = () => { 55 | setUsername(""); 56 | }; 57 | 58 | return ( 59 | <> 60 |

61 | {username} {lastname} 62 |

63 | setUsername(e.target.value)} /> 64 | setLastname(e.target.value)} /> 65 | Reset name 66 | 67 | ); 68 | }; 69 | ``` 70 | 71 | - If we run the sample we will check that the render is always triggered 72 | (_resetNameCallback_ is always recreated, shallow compare will fail). 73 | 74 | - The trick here is to use _React.useCallback_ and passing as a second 75 | argument an empty array (it will just hold the reference for the function 76 | forever). 77 | 78 | 79 | ```diff 80 | import React from "react"; 81 | 82 | export const MyComponent = () => { 83 | const [username, setUsername] = React.useState("John"); 84 | const [lastname, setLastname] = React.useState("Doe"); 85 | 86 | 87 | - const resetNameCallback = () => {setUsername('');} 88 | + const resetNameCallback = React.useCallback(() => setUsername(''), []); 89 | 90 | return ( 91 | <> 92 |

93 | {username} {lastname} 94 |

95 | setUsername(e.target.value)} /> 96 | setLastname(e.target.value)} /> 97 | Reset name 98 | 99 | ); 100 | }; 101 | 102 | const ResetValue = React.memo(props => { 103 | console.log( 104 | "Hey I'm only rendered the first time, check React.memo + callback" 105 | ); 106 | 107 | return ( 108 | 109 | ); 110 | }); 111 | ``` 112 | 113 | - If we run he example, we can see that the rerender is no longer launched in the component. 114 | 115 | How does this work? _useCallback_ will return a function that was originally created, and it returns this instead of creating a new one in each render, we achive this by passing an empty array as a second argument(as we did with _React.useEffect_) and if we omit the second parameter, this function will be reevaluated after each render. 116 | 117 | # About Basefactor + Lemoncode 118 | 119 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 120 | 121 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 122 | 123 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 124 | 125 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 126 | -------------------------------------------------------------------------------- /09-pure-component-callback/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/09-pure-component-callback/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/09-pure-component-callback/Readme.md) 6 | 7 |
8 |
9 | 10 | # 09 Pure component callback 11 | 12 | ## Resumen 13 | 14 | Este ejemplo toma como punto de partida el ejemplo [_08-pure-component_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component). 15 | 16 | ## Paso a Paso 17 | 18 | - Primero copiamos el ejemplo anterior, y hacemos un _npm install_ 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | - Vamos a añadir como punto de partida un componente padre 25 | que nos permite editar un nombre y apellido y un componente 26 | hijo que sirve para poner los valores de nombre y apellido a blanco. 27 | 28 | _./src/demo.tsx_ 29 | 30 | ```tsx 31 | import React from "react"; 32 | 33 | interface Props { 34 | onReset: () => void; 35 | } 36 | 37 | const ResetValue: React.FC = React.memo((props) => { 38 | console.log( 39 | "Hey I'm only rendered the first time, check React.memo + callback" 40 | ); 41 | 42 | return ; 43 | }); 44 | 45 | export const MyComponent = () => { 46 | const [username, setUsername] = React.useState("John"); 47 | const [lastname, setLastname] = React.useState("Doe"); 48 | 49 | const resetNameCallback = () => { 50 | setUsername(""); 51 | }; 52 | 53 | return ( 54 | <> 55 |

56 | {username} {lastname} 57 |

58 | setUsername(e.target.value)} /> 59 | setLastname(e.target.value)} /> 60 | Reset name 61 | 62 | ); 63 | }; 64 | ``` 65 | 66 | - Si ejecutamos el ejemplo, podemos ver que el render del componente 67 | _ResetValue_ se lanza cuando modificamos el campo nombre o el de apellido 68 | ¿ Cómo puede ser esto posible si sólo le pasamos como propiedad _resetNameCallback_ 69 | y tenemos el componente envuelto en un _React.memo_. 70 | 71 | Si pusiermoas el modo detective y utilizaramos el hook de ayuda _whyDidYouUpdate_ 72 | nos daríamos cuenta que el culpable es la funcióna: _resetNameCallback_ 73 | ¿Por que? porque se crea una nueva en cada render... así _React.memo_ dispara el 74 | render porque el puntero a la propiedad cambia. 75 | 76 | ¿ Qué podemos hacer para solucionar esto? Utilizar el hook _React.useCallback_ 77 | y tal como en _React.useEffect_ pasarle como segundo parametro un array vacio. 78 | 79 | ```diff 80 | import React from "react"; 81 | 82 | export const MyComponent = () => { 83 | const [username, setUsername] = React.useState("John"); 84 | const [lastname, setLastname] = React.useState("Doe"); 85 | 86 | 87 | - const resetNameCallback = () => {setUsername('');} 88 | + const resetNameCallback = React.useCallback(() => setUsername(''), []); 89 | 90 | return ( 91 | <> 92 |

93 | {username} {lastname} 94 |

95 | setUsername(e.target.value)} /> 96 | setLastname(e.target.value)} /> 97 | Reset name 98 | 99 | ); 100 | }; 101 | 102 | const ResetValue = React.memo(props => { 103 | console.log( 104 | "Hey I'm only rendered the first time, check React.memo + callback" 105 | ); 106 | 107 | return ( 108 | 109 | ); 110 | }); 111 | ``` 112 | 113 | - Si ejecutamos el ejemplo, podemos ver que ya no se lanza el rerender en el componente _ResetValue_ 114 | 115 | ¿ Cómo funciona esto? _useCallback_ guarda la función que se creo originalmente, 116 | y devuelve esta en vez de crear una nueva en cada render, esto lo conseguimos 117 | pasandole un array vacio como segundo parametro (como hacíamos con _React.useEffect_) 118 | si queremos que se reevalue dependiendo del valor de una propiedad o estado, podemos 119 | añadirlas al segundo aprametro de este callbakc (al igual que con useEffect), y si 120 | omitimos el segundo parametro, esta función se reevaluara después de cada render. 121 | 122 | # ¿Te apuntas a nuestro máster? 123 | 124 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 125 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 126 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 127 | con clases en vivo, como edición continua con mentorización, para 128 | que puedas ir a tu ritmo y aprender mucho. 129 | 130 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 131 | 132 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 133 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 134 | -------------------------------------------------------------------------------- /09-pure-component-callback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "use-debounce": "^7.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /09-pure-component-callback/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /09-pure-component-callback/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface Props { 4 | onReset: () => void; 5 | } 6 | 7 | const ResetValue: React.FC = React.memo((props) => { 8 | console.log( 9 | "Hey I'm only rendered the first time, check React.memo + callback" 10 | ); 11 | 12 | return ; 13 | }); 14 | 15 | export const MyComponent = () => { 16 | const [username, setUsername] = React.useState("John"); 17 | const [lastname, setLastname] = React.useState("Doe"); 18 | 19 | const resetNameCallback = React.useCallback(() => setUsername(""), []); 20 | 21 | return ( 22 | <> 23 |

24 | {username} {lastname} 25 |

26 | setUsername(e.target.value)} /> 27 | setLastname(e.target.value)} /> 28 | Reset name 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /09-pure-component-callback/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /09-pure-component-callback/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /09-pure-component-callback/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /09-pure-component-callback/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /09-pure-component-callback/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /10-use-reducer/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /10-use-reducer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "use-debounce": "^7.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /10-use-reducer/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /10-use-reducer/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface UserState { 4 | name: string; 5 | lastname: string; 6 | } 7 | 8 | interface Action { 9 | type: string; 10 | payload: any; 11 | } 12 | 13 | const actionIds = { 14 | setName: "setname", 15 | setLastname: "setlastname", 16 | }; 17 | 18 | const userInfoReducer = (state: UserState, action: Action): UserState => { 19 | switch (action.type) { 20 | case actionIds.setName: 21 | return { 22 | ...state, 23 | name: action.payload, 24 | }; 25 | case actionIds.setLastname: 26 | return { 27 | ...state, 28 | lastname: action.payload, 29 | }; 30 | default: 31 | return state; 32 | } 33 | }; 34 | 35 | interface Props { 36 | name: string; 37 | dispatch: React.Dispatch; 38 | } 39 | 40 | const EditUsername: React.FC = React.memo((props) => { 41 | console.log( 42 | "Hey I'm only rerendered when name gets updated, check React.memo" 43 | ); 44 | 45 | return ( 46 | 49 | props.dispatch({ type: actionIds.setName, payload: e.target.value }) 50 | } 51 | /> 52 | ); 53 | }); 54 | 55 | export const MyComponent = () => { 56 | const [userInfo, dispatch] = React.useReducer(userInfoReducer, { 57 | name: "John", 58 | lastname: "Doe", 59 | }); 60 | 61 | return ( 62 | <> 63 |

64 | {userInfo.name} {userInfo.lastname} 65 |

66 | 67 | 70 | dispatch({ type: actionIds.setLastname, payload: e.target.value }) 71 | } 72 | /> 73 | 74 | ); 75 | }; 76 | -------------------------------------------------------------------------------- /10-use-reducer/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /10-use-reducer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /10-use-reducer/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /10-use-reducer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /10-use-reducer/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /11-use-context/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /11-use-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "use-debounce": "^7.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /11-use-context/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent, MyContextProvider } from "./demo"; 3 | 4 | export const App = () => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /11-use-context/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface UserContext { 4 | username: string; 5 | setUsername: (value: string) => void; 6 | } 7 | 8 | const MyContext = React.createContext({ 9 | username: "", 10 | setUsername: (value) => {}, 11 | }); 12 | 13 | export const MyContextProvider = (props) => { 14 | const [username, setUsername] = React.useState("John Doe"); 15 | 16 | return ( 17 | 18 | {props.children} 19 | 20 | ); 21 | }; 22 | 23 | const MyEditComponent = () => { 24 | const { username, setUsername } = React.useContext(MyContext); 25 | 26 | const [newUsername, setNewUsername] = React.useState(""); 27 | 28 | const handleChange = (e) => { 29 | setNewUsername(e.target.value); 30 | }; 31 | return ( 32 |
33 | 34 | 35 |
36 | ); 37 | }; 38 | 39 | export const MyComponent = () => { 40 | const myContext = React.useContext(MyContext); 41 | return ( 42 | <> 43 |

{myContext.username}

44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /11-use-context/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /11-use-context/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /11-use-context/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /11-use-context/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /11-use-context/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /12-set-state-func/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /12-set-state-func/Readme.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/12-set-state-func/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/12-set-state-func/Readme.md) 6 | 7 |
8 |
9 | 10 | # 12 set state func 11 | 12 | ## Resume 13 | 14 | This example takes the [_11-use-context_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/11-use-context) example as a starting point. 15 | 16 | ### Short explanation 17 | 18 | An important issue to consider with hooks and functional components 19 | is that the functions are executed once and die ( hooks serve as data stores 20 | among other things), but if we make an asynchronous call, 21 | the closure principle will be applied and if we need to retrieve any 22 | state data it will just hold a frozen value from the past. 23 | 24 | ### Long explanation 25 | 26 | One of the fundamental prerequisites to be able learn React is to have 27 | a solid undertanding of Javascript and ES6. In this case you need to have 28 | a good understanding of the _clousure_ concept. 29 | 30 | Functional components are just that, functions: 31 | 32 | - They are invoked. 33 | - They run. 34 | - They die. 35 | 36 | If we remember the concept of closure, when we launch an asynchronous call it allowed me (in the async response handler) 37 | to access variables of the parent function that even though this function was already dead. 38 | 39 | If we apply this concept to React, we can find a curious case: 40 | 41 | - Imagine that we have a discount value in a state. 42 | - We make an asynchronous call to the server so that it gives us the total of the order. 43 | - While the call is in progress the discount field changes. 44 | - In the server's response we multiply the order total by the discount. 45 | 46 | What discount value do you think will be appled, the old or the new one? ... Ouch!, the 47 | old one, Why? ... let's think about a closure, the parent function execution finished (the 48 | async call is on it's way) the parent function execution is dead, but following the closure 49 | principle, this values will be kept in the heap until the async call is completed, 50 | What happens with the new values? They ​​are generated in _second life_ ... that is, in another call to the function where everything starts again). 51 | 52 | Let's see this with an example. 53 | 54 | ## Steps 55 | 56 | If you come from the _useContext_ example, remember to remove the _provider_ instantiation from _app_ and the extra component you created. 57 | 58 | We replace _demo.tsx_ with the following content: 59 | 60 | _./src/demo.tsx_ 61 | 62 | ```tsx 63 | import React from "react"; 64 | 65 | export const MyComponent: React.FC = () => { 66 | const [number, setNumber] = React.useState(0); 67 | 68 | React.useEffect(() => { 69 | setTimeout(() => { 70 | setNumber(number + 1); 71 | }, 1500); 72 | setNumber(1); 73 | }, []); 74 | 75 | return ( 76 | <> 77 |

Number: {number}

78 | 79 | ); 80 | }; 81 | ``` 82 | 83 | If you run this code you will find some odd behavior? At first glance after a second and a half, the displayed value 84 | should be _2_, what's up? The callback is reading the frozen value not the actual one. 85 | 86 | How can we fix this? the _setState_ function brings a second signature in which we can pass it 87 | a function: 88 | 89 | ```diff 90 | React.useEffect(() => { 91 | setTimeout(() => { 92 | - setNumero(numero + 1); 93 | + setNumero((numero) => numero + 1); 94 | 95 | }, 1500); 96 | setNumero(1); 97 | }, []); 98 | ``` 99 | 100 | When we invoke it this way, the _setState_ hook ensure that the latest available value is being served. 101 | 102 | # About Basefactor + Lemoncode 103 | 104 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 105 | 106 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 107 | 108 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 109 | 110 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 111 | -------------------------------------------------------------------------------- /12-set-state-func/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/12-set-state-func/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/12-set-state-func/Readme.md) 6 | 7 |
8 |
9 | 10 | # 12 set state func 11 | 12 | ## Resumen 13 | 14 | Este ejemplo toma como punto de partida el ejemplo [_11-use-context_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/11-use-context). 15 | 16 | ### Explicación corta 17 | 18 | Un tema importante a tener en cuenta con los hooks y los componentes funcionales 19 | es que las funciones se ejecutan una vez y se mueren (los hooks nos sirven como almacenes de datos entre otras cosas), 20 | pero si hacemos una llamada asíncrona en esa función, por el principio de closure cuando se invoque dicha función, los 21 | datos que tendremos serán los valores de dicha ejecución (se queda zombies) 22 | 23 | ### Explicación larga 24 | 25 | Uno de los prerequisitos fundamentales para poder aprender React es tener 26 | unos conocimientos sólidos de Javascript y ES6. En este caso tener muy 27 | claro el concepto de _closure_. 28 | 29 | Los componentes funcionales son eso, funciones: 30 | 31 | - Se invocan. 32 | - Se ejecutan. 33 | - Mueren. 34 | 35 | Si recordamos el concepto de closure, cuando tenía una llamada asíncrona 36 | me permitía en la respuesta acceder a variables de la funcíon padre que la 37 | había invocado aunque está función estuviera ya muerta. 38 | 39 | Si aplicamos este concepto a React, nos podemos encontrar con un caso curioso: 40 | 41 | - Imagina que tenemos en un estado un valor de descuento. 42 | - Hacemos una llamada asíncrona al servidor para que nos de el total del pedido. 43 | - Mientras que la llamada está en curso el campo de descuento cambia. 44 | - En la respuesta del servidor multiplicamos el total del pedido por el descuento. 45 | 46 | ¿ Qué valor de descuento crees que aplicará el antiguo o el nuevo? ... Bingo, el 47 | antiguo ¿Porqué? ... pensemnos en un closure, no dejamos de tener una función padre 48 | que se ha muerto, que mantiene los valores por el principio de closure y que lee 49 | los valores que tuviera en ese momento ¿ Qué pasa con los nuevos valores se generan 50 | en otra vida... es decir en otra llamada a la función donde todo vuelve a arrancar). 51 | 52 | Veamos esto con un ejemplo. 53 | 54 | ## Pasos 55 | 56 | Si vienes del ejemplo _useContext_ acuerdate de quitar de _app_ la instanciación del _provider_ y 57 | el componente extra que creaste. 58 | 59 | Sustituimos _demo.tsx_ por el siguiente contenido: 60 | 61 | _./src/demo.tsx_ 62 | 63 | ```tsx 64 | import React from "react"; 65 | 66 | export const MyComponent: React.FC = () => { 67 | const [numero, setNumero] = React.useState(0); 68 | 69 | React.useEffect(() => { 70 | setTimeout(() => { 71 | setNumero(numero + 1); 72 | }, 1500); 73 | setNumero(1); 74 | }, []); 75 | 76 | return ( 77 | <> 78 |

El numero: {numero}

79 | 80 | ); 81 | }; 82 | ``` 83 | 84 | ¿Es normal lo que está pasando? A primera vista después de un segundo y medio, el valor que se muestra 85 | debería de ser _2_, ¿Qué pasa? Que en el callback cuando tira de closure de esa ejecución, el valor 86 | de número es _0_. 87 | 88 | ¿Como podemos corregir esto? la función _setState_ trae una segunda firma en la que podemos pasarle 89 | una función: 90 | 91 | ```diff 92 | React.useEffect(() => { 93 | setTimeout(() => { 94 | - setNumero(numero + 1); 95 | + setNumero((numero) => numero + 1); 96 | 97 | }, 1500); 98 | setNumero(1); 99 | }, []); 100 | ``` 101 | 102 | Cuando la invocamos de esta manera, el hook de _setState_ se asegura de traernos el último valor. 103 | 104 | # ¿Te apuntas a nuestro máster? 105 | 106 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 107 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 108 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 109 | con clases en vivo, como edición continua con mentorización, para 110 | que puedas ir a tu ritmo y aprender mucho. 111 | 112 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 113 | 114 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 115 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 116 | -------------------------------------------------------------------------------- /12-set-state-func/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /12-set-state-func/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /12-set-state-func/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyComponent: React.FC = () => { 4 | const [numero, setNumero] = React.useState(0); 5 | 6 | React.useEffect(() => { 7 | setTimeout(() => { 8 | setNumero((numero) => numero + 1); 9 | }, 1500); 10 | setNumero(1); 11 | }, []); 12 | 13 | return ( 14 | <> 15 |

El numero: {numero}

16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /12-set-state-func/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /12-set-state-func/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /12-set-state-func/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /12-set-state-func/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /12-set-state-func/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /13-async-closure/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /13-async-closure/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /13-async-closure/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /13-async-closure/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyComponent = () => { 4 | const [info, setInfo] = React.useState({ 5 | message: "initial message", 6 | seconds: 0, 7 | }); 8 | 9 | React.useEffect(() => { 10 | setTimeout(() => { 11 | console.log(info.seconds); 12 | setInfo((info) => ({ ...info, seconds: 1 })); 13 | }, 1000); 14 | 15 | setTimeout(() => { 16 | setInfo((info) => ({ 17 | ...info, 18 | message: `Total seconds: ${info.seconds}`, 19 | })); 20 | }, 2000); 21 | }, []); 22 | 23 | return ( 24 | <> 25 |

{info.message}

26 |

{info.seconds}

27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /13-async-closure/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /13-async-closure/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /13-async-closure/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /13-async-closure/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /13-async-closure/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /14-use-ref-dom/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /14-use-ref-dom/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | [español](https://lemoncode.net/) 3 | 4 | 5 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/14-use-ref-dom/Readme_es.md) 6 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/14-use-ref-dom/Readme.md) 7 | 8 |
9 |
10 | 11 | # 14 React.useRef DOM 12 | 13 | In the previous example we introduced the hook _userRef_, in this example 14 | we are going to use it to access a node of the DOM. 15 | 16 | # Steps 17 | 18 | - We take as a starting point [_13-async-closure_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/13-async-closure), we copy the content 19 | and we do a _npm install_. 20 | 21 | ```bash 22 | npm install 23 | ``` 24 | 25 | - In _demo.tsx_ we paste the following code (note that here we define 26 | the _useRef_ and associate it in the _div_ container) 27 | 28 | _./src/demo.tsx_ 29 | 30 | ```tsx 31 | import React from "react"; 32 | 33 | export const MyComponent = () => { 34 | const containerElementRef = React.useRef(null); 35 | 36 | return
; 37 | }; 38 | ``` 39 | 40 | _./styles.css_ 41 | 42 | ```diff 43 | .App { 44 | font-family: sans-serif; 45 | text-align: center; 46 | } 47 | 48 | +.container { 49 | + border: 1px solid steelblue; 50 | + margin: 15px; 51 | + padding: 50px; 52 | +} 53 | ``` 54 | 55 | 56 | - In this example we are going to show the current width of the container using the associated _ref_ to the element of the _dom_ 57 | 58 | ```diff 59 | import React from "react"; 60 | 61 | export const MyComponent = () => { 62 | const containerElementRef = React.useRef(null); 63 | + const [message, setMessage] = React.useState( 64 | + "Click button to get container width" 65 | + ); 66 | 67 | + const calculateContainerWidth = () => { 68 | + setMessage(`Container width: ${containerElementRef.current.clientWidth}px`); 69 | + }; 70 | 71 | return ( 72 |
73 | +

{message}

74 | + 77 |
78 | ); 79 | }; 80 | 81 | ``` 82 | 83 | - If we execute, we can see how it now gives us the current result when we press 84 | on the button. 85 | 86 | # About Basefactor + Lemoncode 87 | 88 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 89 | 90 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 91 | 92 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 93 | 94 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 95 | -------------------------------------------------------------------------------- /14-use-ref-dom/Readme_es.md: -------------------------------------------------------------------------------- 1 | [español](https://lemoncode.net/) 2 | 3 | 4 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/14-use-ref-dom/Readme_es.md) 5 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/14-use-ref-dom/Readme.md) 6 | 7 |
8 |
9 | 10 | 11 | # 14 React.useRef DOM 12 | 13 | En el ejemplo anterior introdujimos el hook _userRef_, en este ejemplo 14 | vamos a usarlo para acceder a un nodo del dom. 15 | 16 | # Pasos 17 | 18 | - Tomamos como punto de partida [_13-async-closure_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/13-async-closure), copiamos el contenido 19 | y hacemos un _npm install_. 20 | 21 | ```bash 22 | npm install 23 | ``` 24 | 25 | - En _demo.js_ pegamos el siguiente código (fijate que aquí definimos 26 | el _useRef_ y lo asociamos en el _div_ container). 27 | 28 | _./src/demo.tsx_ 29 | 30 | ```tsx 31 | import React from "react"; 32 | 33 | export const MyComponent = () => { 34 | const containerElementRef = React.useRef(null); 35 | 36 | return
; 37 | }; 38 | ``` 39 | 40 | - Vamos a darle un estilo a ese div para distinguirlo. 41 | 42 | _./styles.css_ 43 | 44 | ```diff 45 | .my-text { 46 | color: blue; 47 | } 48 | 49 | +.container { 50 | + border: 1px solid steelblue; 51 | + margin: 15px; 52 | + padding: 50px; 53 | +} 54 | ``` 55 | 56 | - En este ejemplo vamos a mostrar el ancho actual del contenedor usando el _ref_ asociado 57 | a este elemento del _dom_ 58 | 59 | ```diff 60 | import React from "react"; 61 | 62 | export const MyComponent = () => { 63 | const containerElementRef = React.useRef(null); 64 | + const [message, setMessage] = React.useState( 65 | + "Click button to get container width" 66 | + ); 67 | 68 | + const calculateContainerWidth = () => { 69 | + setMessage(`Container width: ${containerElementRef.current.clientWidth}px`); 70 | + }; 71 | 72 | return ( 73 |
74 | +

{message}

75 | + 78 |
79 | ); 80 | }; 81 | ``` 82 | 83 | - Si ejecutamos, podemos ver como ahora nos da el resultado actual cuando pulsamos 84 | en el botón. 85 | 86 | # ¿Te apuntas a nuestro máster? 87 | 88 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 89 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 90 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 91 | con clases en vivo, como edición continua con mentorización, para 92 | que puedas ir a tu ritmo y aprender mucho. 93 | 94 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 95 | 96 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 97 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 98 | -------------------------------------------------------------------------------- /14-use-ref-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /14-use-ref-dom/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /14-use-ref-dom/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyComponent = () => { 4 | const containerElementRef = React.useRef(null); 5 | const [message, setMessage] = React.useState( 6 | "Click button to get container width" 7 | ); 8 | 9 | const calculateContainerWidth = () => { 10 | setMessage(`Container width: ${containerElementRef.current.clientWidth}px`); 11 | }; 12 | 13 | return ( 14 |
15 |

{message}

16 | 19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /14-use-ref-dom/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /14-use-ref-dom/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /14-use-ref-dom/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | 5 | .container { 6 | border: 1px solid steelblue; 7 | margin: 15px; 8 | padding: 50px; 9 | } 10 | -------------------------------------------------------------------------------- /14-use-ref-dom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /14-use-ref-dom/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /15-promise-unmounted/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /15-promise-unmounted/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /15-promise-unmounted/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | 4 | export const App = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /15-promise-unmounted/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MyComponent = () => { 4 | const [visible, setVisible] = React.useState(false); 5 | 6 | return ( 7 | <> 8 | {visible && } 9 | 12 | 13 | ); 14 | }; 15 | 16 | const useSafeState = function ( 17 | initialValue: T 18 | ): [T, React.Dispatch>] { 19 | const mountedRef = React.useRef(false); 20 | 21 | const [state, setState] = React.useState(initialValue); 22 | 23 | React.useEffect(() => { 24 | mountedRef.current = true; 25 | return () => { 26 | mountedRef.current = false; 27 | }; 28 | }, []); 29 | 30 | const isMounted = () => mountedRef.current; 31 | 32 | const setSafeState = function ( 33 | data: T 34 | ): React.Dispatch> | void { 35 | return isMounted() ? setState(data) : null; 36 | }; 37 | 38 | return [state, setSafeState] 39 | }; 40 | 41 | export const MyChildComponent = () => { 42 | const [filter, setFilter] = React.useState(""); 43 | const [userCollection, setUserCollection] = useSafeState([]); 44 | 45 | // Load full list when the component gets mounted and filter gets updated 46 | React.useEffect(() => { 47 | setTimeout(() => { 48 | fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`) 49 | .then((response) => response.json()) 50 | .then((json) => setUserCollection(json)); 51 | }, 2500); 52 | }, [filter]); 53 | 54 | return ( 55 |
56 | setFilter(e.target.value)} /> 57 |
    58 | {userCollection.map((user, index) => ( 59 |
  • {user.name}
  • 60 | ))} 61 |
62 |
63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /15-promise-unmounted/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /15-promise-unmounted/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /15-promise-unmounted/src/styles.css: -------------------------------------------------------------------------------- 1 | .my-text { 2 | color: blue; 3 | } 4 | 5 | .container { 6 | border: 1px solid steelblue; 7 | margin: 15px; 8 | padding: 50px; 9 | } 10 | -------------------------------------------------------------------------------- /15-promise-unmounted/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /15-promise-unmounted/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | loader: "url-loader", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /16-memo-predicate/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /16-memo-predicate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /16-memo-predicate/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | import "./styles.css"; 4 | 5 | export const App = () => { 6 | const [satisfactionLevel, setSatisfactionLevel] = React.useState(300); 7 | return ( 8 |
9 | setSatisfactionLevel(+event.target.value)} 15 | /> 16 |
17 | {satisfactionLevel} 18 |
19 | 20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /16-memo-predicate/src/assets/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/16-memo-predicate/src/assets/five.png -------------------------------------------------------------------------------- /16-memo-predicate/src/assets/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/16-memo-predicate/src/assets/four.png -------------------------------------------------------------------------------- /16-memo-predicate/src/assets/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/16-memo-predicate/src/assets/one.png -------------------------------------------------------------------------------- /16-memo-predicate/src/assets/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/16-memo-predicate/src/assets/three.png -------------------------------------------------------------------------------- /16-memo-predicate/src/assets/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/16-memo-predicate/src/assets/two.png -------------------------------------------------------------------------------- /16-memo-predicate/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const setSatisfactionClass = (level) => { 4 | if (level < 100) { 5 | return "very-dissatisfied"; 6 | } 7 | 8 | if (level < 200) { 9 | return "somewhat-dissatisfied"; 10 | } 11 | 12 | if (level < 300) { 13 | return "neither"; 14 | } 15 | 16 | if (level < 400) { 17 | return "somewhat-satisfied"; 18 | } 19 | 20 | return "very-satisfied"; 21 | }; 22 | 23 | const isSameRange = (prevValue, nextValue) => { 24 | const prevValueClass = setSatisfactionClass(prevValue.level); 25 | const nextValueClass = setSatisfactionClass(nextValue.level); 26 | 27 | return prevValueClass === nextValueClass; 28 | }; 29 | 30 | interface Props { 31 | level: number; 32 | } 33 | 34 | export const MyComponent: React.FC = React.memo((props) => { 35 | const { level } = props; 36 | 37 | return
; 38 | }, isSameRange); 39 | -------------------------------------------------------------------------------- /16-memo-predicate/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /16-memo-predicate/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /16-memo-predicate/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | 6 | .very-dissatisfied { 7 | width: 100%; 8 | height: 80px; 9 | background: url("./assets/one.png") no-repeat center; 10 | } 11 | 12 | .somewhat-dissatisfied { 13 | width: 100%; 14 | height: 80px; 15 | background: url("./assets/two.png") no-repeat center; 16 | } 17 | 18 | .neither { 19 | width: 100%; 20 | height: 80px; 21 | background: url("./assets/three.png") no-repeat center; 22 | } 23 | 24 | .somewhat-satisfied { 25 | width: 100%; 26 | height: 80px; 27 | background-color: aqua; 28 | background: url("./assets/four.png") no-repeat center; 29 | } 30 | 31 | .very-satisfied { 32 | width: 100%; 33 | height: 80px; 34 | background: url("./assets/five.png") no-repeat center; 35 | } 36 | -------------------------------------------------------------------------------- /16-memo-predicate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /16-memo-predicate/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | type: "asset/resource", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /17-use-debug-value/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /17-use-debug-value/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | [español](https://lemoncode.net/) 3 | 4 | 5 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/17-use-debug-value/Readme_es.md) 6 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/17-use-debug-value/Readme.md) 7 | 8 |
9 |
10 | 11 | 12 | # 17 Use Debug Value 13 | 14 | The internal hook _useDebugValue_ is useful for debugging custom hooks using the React DevTools. This hook allows you to display a label for a custom hook. 15 | 16 | # Steps 17 | 18 | - We will take as a starting point the example [_16-memo-predicate_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/16-memo-predicate). Copy the contents of the project to a new folder and run _npm install_. 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | - Let's open the file _app.tsx_. Let's add the following content. 25 | _./src/app.tsx_ 26 | 27 | ```tsx 28 | import React from "react"; 29 | import { MyComponent } from "./demo"; 30 | import "./styles.css"; 31 | 32 | export const App = () => { 33 | return ( 34 |
35 | 36 |
37 | ); 38 | }; 39 | ``` 40 | 41 | - Let's also open _styles.css_. Add this content. 42 | 43 | ```css 44 | .App { 45 | font-family: sans-serif; 46 | text-align: center; 47 | } 48 | ``` 49 | 50 | - Let's open the file _demo.tsx_. Let's add the content that appears just below (a custom hook that saves a user and stores their TO-DO's). 51 | 52 | _./src/demo.tsx_ 53 | 54 | ```tsx 55 | import React from "react"; 56 | 57 | const useUserTodos = () => { 58 | const [user, setUser] = React.useState(""); 59 | const [userTodos, setUserTodos] = React.useState([]); 60 | 61 | const loadTodos = () => { 62 | fetch(`https://jsonplaceholder.typicode.com/todos?userId=${user}`) 63 | .then((response) => response.json()) 64 | .then((json) => { 65 | console.log(userTodos); 66 | setUserTodos(json); 67 | }); 68 | }; 69 | 70 | return { user, setUser, userTodos, loadTodos }; 71 | }; 72 | 73 | export const MyComponent = () => { 74 | const { user, setUser, userTodos, loadTodos } = useUserTodos(); 75 | 76 | React.useEffect(() => { 77 | loadTodos(); 78 | }, [user]); 79 | 80 | return ( 81 |
82 | setUser(e.target.value)} /> 83 |

User {user} TO-DO's:

84 |
    85 | {userTodos.map((todo, index) => ( 86 |
  • 87 | {todo.title} 88 | {todo.completed ? <>✔ : <>✖} 89 |
  • 90 | ))} 91 |
92 |
93 | ); 94 | }; 95 | ``` 96 | 97 | 98 | - Now we have to add the _useDebugValue_ hook. In this case we will show a label indicating the content of _user_. 99 | 100 | _./src/demo.tsx_ 101 | 102 | ```diff 103 | import React from "react"; 104 | 105 | const useUserTodos = () => { 106 | const [user, setUser] = React.useState(""); 107 | const [userTodos, setUserTodos] = React.useState([]); 108 | 109 | const loadTodos = () => { 110 | fetch(`https://jsonplaceholder.typicode.com/todos?userId=${user}`) 111 | .then(response => response.json()) 112 | .then(json => { 113 | console.log(userTodos); 114 | setUserTodos(json); 115 | }); 116 | }; 117 | 118 | + React.useDebugValue(user !== "" ? `User ${user}` : 'No user'); 119 | 120 | return { user, setUser, userTodos, loadTodos }; 121 | }; 122 | 123 | export const MyComponent = () => { 124 | const { user, setUser, userTodos, loadTodos } = useUserTodos(); 125 | 126 | React.useEffect(() => { 127 | loadTodos(); 128 | }, [user]); 129 | 130 | return ( 131 |
132 | setUser(e.target.value)} /> 133 |

User {user} TO-DO's:

134 |
    135 | {userTodos.map((todo, index) => ( 136 |
  • 137 | {todo.title} 138 | {todo.completed ? <>✔ : <>✖} 139 |
  • 140 | ))} 141 |
142 |
143 | ); 144 | }; 145 | ``` 146 | 147 | - Execute the example 148 | 149 | ```bash 150 | npm start 151 | ``` 152 | 153 | - Let's open the React DevTools in Chrome or Firefox. In the _hooks_ section you can check the debug value of the custom hook. 154 | 155 | ![01-dev-tools](./resources/01-dev-tools.png) 156 | 157 | # About Basefactor + Lemoncode 158 | 159 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 160 | 161 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 162 | 163 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 164 | 165 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 166 | -------------------------------------------------------------------------------- /17-use-debug-value/Readme_es.md: -------------------------------------------------------------------------------- 1 | 2 | [español](https://lemoncode.net/) 3 | 4 | 5 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/17-use-debug-value/Readme_es.md) 6 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/17-use-debug-value/Readme.md) 7 | 8 |
9 |
10 | 11 | 12 | # 17 Use Debug Value 13 | 14 | El hook interno _useDebugValue_ es útil para depurar custom hooks usando las React DevTools. Este hook te permite mostrar una etiqueta para un custom hook. 15 | 16 | # Pasos 17 | 18 | - Tomaremos como punto de partida el ejemplo [_16-memo-predicate_](https://github.com/Lemoncode/react-hooks-by-example/blob/master/16-memo-predicate). Copia el contenido del proyecto a una carpeta nueva y ejecuta _npm install_. 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | - Abramos el fichero _app.tsx_. Añadamos le siguiente contendio. 25 | 26 | _./src/app.tsx_ 27 | 28 | ```tsx 29 | import React from "react"; 30 | import { MyComponent } from "./demo"; 31 | import "./styles.css"; 32 | 33 | export const App = () => { 34 | return ( 35 |
36 | 37 |
38 | ); 39 | }; 40 | ``` 41 | 42 | - Abramos también _styles.css_. Añados este contenido. 43 | 44 | _./src/styles.css_ 45 | 46 | ```css 47 | .App { 48 | font-family: sans-serif; 49 | text-align: center; 50 | } 51 | ``` 52 | 53 | - Abramos el fichero _demo.tsx_. Añadamos el contenido que aparece justo aquí abajo (un custom hook que guarda un usuario y almacena sus TO-DO's). 54 | 55 | _./src/demo.tsx_ 56 | 57 | ```tsx 58 | import React from "react"; 59 | 60 | const useUserTodos = () => { 61 | const [user, setUser] = React.useState(""); 62 | const [userTodos, setUserTodos] = React.useState([]); 63 | 64 | const loadTodos = () => { 65 | fetch(`https://jsonplaceholder.typicode.com/todos?userId=${user}`) 66 | .then((response) => response.json()) 67 | .then((json) => { 68 | console.log(userTodos); 69 | setUserTodos(json); 70 | }); 71 | }; 72 | 73 | return { user, setUser, userTodos, loadTodos }; 74 | }; 75 | 76 | export const MyComponent = () => { 77 | const { user, setUser, userTodos, loadTodos } = useUserTodos(); 78 | 79 | React.useEffect(() => { 80 | loadTodos(); 81 | }, [user]); 82 | 83 | return ( 84 |
85 | setUser(e.target.value)} /> 86 |

User {user} TO-DO's:

87 |
    88 | {userTodos.map((todo, index) => ( 89 |
  • 90 | {todo.title} 91 | {todo.completed ? <>✔ : <>✖} 92 |
  • 93 | ))} 94 |
95 |
96 | ); 97 | }; 98 | ``` 99 | 100 | - Ahora tenemos que añadir el hook _useDebugValue_. En este caso mostraremos una etiqueta indicando el contenido de _user_. 101 | 102 | _./src/demo.tsx_ 103 | 104 | ```diff 105 | import React from "react"; 106 | 107 | const useUserTodos = () => { 108 | const [user, setUser] = React.useState(""); 109 | const [userTodos, setUserTodos] = React.useState([]); 110 | 111 | const loadTodos = () => { 112 | fetch(`https://jsonplaceholder.typicode.com/todos?userId=${user}`) 113 | .then(response => response.json()) 114 | .then(json => { 115 | console.log(userTodos); 116 | setUserTodos(json); 117 | }); 118 | }; 119 | 120 | + React.useDebugValue(user !== "" ? `User ${user}` : 'No user'); 121 | 122 | return { user, setUser, userTodos, loadTodos }; 123 | }; 124 | 125 | export const MyComponent = () => { 126 | const { user, setUser, userTodos, loadTodos } = useUserTodos(); 127 | 128 | React.useEffect(() => { 129 | loadTodos(); 130 | }, [user]); 131 | 132 | return ( 133 |
134 | setUser(e.target.value)} /> 135 |

User {user} TO-DO's:

136 |
    137 | {userTodos.map((todo, index) => ( 138 |
  • 139 | {todo.title} 140 | {todo.completed ? <>✔ : <>✖} 141 |
  • 142 | ))} 143 |
144 |
145 | ); 146 | }; 147 | ``` 148 | 149 | - Ejecuta el ejemplo 150 | 151 | ```bash 152 | npm start 153 | ``` 154 | 155 | - Abre las React DevTools en Chrome o Firefox. En la sección _hooks_ puedes ver el valor de depuración de el custom hook. 156 | 157 | ![01-dev-tools](./resources/01-dev-tools.png) 158 | 159 | # ¿Te apuntas a nuestro máster? 160 | 161 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 162 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 163 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 164 | con clases en vivo, como edición continua con mentorización, para 165 | que puedas ir a tu ritmo y aprender mucho. 166 | 167 | Si lo que te gusta es el mundo del _backend_ también puedes apuntante a nuestro [Bootcamp backend Online Lemoncode](https://lemoncode.net/bootcamp-backend#bootcamp-backend/inicio) 168 | 169 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 170 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 171 | -------------------------------------------------------------------------------- /17-use-debug-value/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /17-use-debug-value/resources/01-dev-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/17-use-debug-value/resources/01-dev-tools.png -------------------------------------------------------------------------------- /17-use-debug-value/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | import "./styles.css"; 4 | 5 | export const App = () => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /17-use-debug-value/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const useUserTodos = () => { 4 | const [user, setUser] = React.useState(""); 5 | const [userTodos, setUserTodos] = React.useState([]); 6 | 7 | const loadTodos = () => { 8 | fetch(`https://jsonplaceholder.typicode.com/todos?userId=${user}`) 9 | .then(response => response.json()) 10 | .then(json => { 11 | console.log(userTodos); 12 | setUserTodos(json); 13 | }); 14 | }; 15 | 16 | React.useDebugValue(user !== "" ? `User ${user}` : 'No user'); 17 | 18 | return { user, setUser, userTodos, loadTodos }; 19 | }; 20 | 21 | export const MyComponent = () => { 22 | const { user, setUser, userTodos, loadTodos } = useUserTodos(); 23 | 24 | React.useEffect(() => { 25 | loadTodos(); 26 | }, [user]); 27 | 28 | return ( 29 |
30 | setUser(e.target.value)} /> 31 |

User {user} TO-DO's:

32 |
    33 | {userTodos.map((todo, index) => ( 34 |
  • 35 | {todo.title} 36 | {todo.completed ? <>✔ : <>✖} 37 |
  • 38 | ))} 39 |
40 |
41 | ); 42 | }; -------------------------------------------------------------------------------- /17-use-debug-value/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /17-use-debug-value/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /17-use-debug-value/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /17-use-debug-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /17-use-debug-value/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | type: "asset/resource", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /18-why-did-you-update/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /18-why-did-you-update/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "run-p -l type-check:watch start:dev", 8 | "type-check": "tsc --noEmit", 9 | "type-check:watch": "npm run type-check -- --watch", 10 | "start:dev": "webpack-dev-server --mode development --open", 11 | "build": "rimraf dist && webpack --mode development" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/cli": "^7.15.7", 17 | "@babel/core": "^7.15.8", 18 | "@babel/preset-env": "^7.15.8", 19 | "@babel/preset-react": "^7.14.5", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@types/react": "^17.0.32", 22 | "@types/react-dom": "^17.0.10", 23 | "babel-loader": "^8.2.3", 24 | "css-loader": "^6.4.0", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.0.0", 27 | "html-webpack-plugin": "^5.4.0", 28 | "npm-run-all": "^4.1.5", 29 | "rimraf": "^3.0.2", 30 | "style-loader": "^3.3.1", 31 | "typescript": "^4.4.4", 32 | "url-loader": "^4.1.1", 33 | "webpack": "^5.59.1", 34 | "webpack-cli": "^4.9.1", 35 | "webpack-dev-server": "^4.3.1" 36 | }, 37 | "dependencies": { 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /18-why-did-you-update/resources/01-message-increment-id.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/18-why-did-you-update/resources/01-message-increment-id.gif -------------------------------------------------------------------------------- /18-why-did-you-update/resources/01-message-increment-id.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/18-why-did-you-update/resources/01-message-increment-id.webm -------------------------------------------------------------------------------- /18-why-did-you-update/resources/02-message-only-cont.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/18-why-did-you-update/resources/02-message-only-cont.gif -------------------------------------------------------------------------------- /18-why-did-you-update/resources/02-message-only-cont.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/react-hooks-by-example/a11ea6d2b809a4339ab728630780a6ed8c23f413/18-why-did-you-update/resources/02-message-only-cont.webm -------------------------------------------------------------------------------- /18-why-did-you-update/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MyComponent } from "./demo"; 3 | import "./styles.css"; 4 | 5 | export const App = () => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /18-why-did-you-update/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const name = { 4 | firstname: "John", 5 | lastname: "Doe", 6 | }; 7 | 8 | export const MyComponent = () => { 9 | const [cont, setCont] = React.useState(0); 10 | const [id, setId] = React.useState(0); 11 | 12 | return ( 13 |
14 |
15 | 16 | 17 |
18 | 19 |
20 |
id: {id}
21 | 22 |
23 |
24 | ); 25 | }; 26 | 27 | interface Name { 28 | firstname: string; 29 | lastname: string; 30 | } 31 | 32 | interface Props { 33 | name: Name; 34 | cont: number; 35 | } 36 | 37 | export const MyChildComponent: React.FC = React.memo((props) => { 38 | useWhyDidYouUpdate("MyChildComponent", props); 39 | return ( 40 |
41 | {props.name.firstname} {props.name.lastname} cont: {props.cont} 42 |
43 | ); 44 | }); 45 | 46 | // Hook 47 | function useWhyDidYouUpdate(name, props) { 48 | // Get a mutable ref object where we can store props ... 49 | // ... for comparison next time this hook runs. 50 | const previousProps = React.useRef(); 51 | 52 | React.useEffect(() => { 53 | if (previousProps.current) { 54 | // Get all keys from previous and current props 55 | const allKeys = Object.keys({ 56 | ...(previousProps.current as any), 57 | ...props, 58 | }); 59 | 60 | // Use this object to keep track of changed props 61 | const changesObj = {}; 62 | // Iterate through keys 63 | allKeys.forEach((key) => { 64 | // If previous is different from current 65 | if ( 66 | previousProps && 67 | previousProps.current && 68 | previousProps.current[key] !== props[key] 69 | ) { 70 | // Add to changesObj 71 | changesObj[key] = { 72 | from: previousProps.current[key], 73 | to: props[key], 74 | }; 75 | } 76 | }); 77 | 78 | // If changesObj not empty then output to console 79 | if (Object.keys(changesObj).length) { 80 | console.log("[why-did-you-update]", name, changesObj); 81 | } 82 | } 83 | 84 | // Finally update previousProps with current props for next hook call 85 | previousProps.current = props; 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /18-why-did-you-update/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /18-why-did-you-update/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { App } from "./app"; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById("root") 10 | ); 11 | -------------------------------------------------------------------------------- /18-why-did-you-update/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /18-why-did-you-update/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /18-why-did-you-update/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const basePath = __dirname; 4 | 5 | module.exports = { 6 | context: path.join(basePath, "src"), 7 | resolve: { 8 | extensions: [".js", ".ts", ".tsx"], 9 | }, 10 | entry: { 11 | app: ["./index.tsx", "./styles.css"], 12 | }, 13 | devtool: "eval-source-map", 14 | stats: "errors-only", 15 | output: { 16 | filename: "[name].[chunkhash].js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | }, 25 | { 26 | test: /\.(png|jpg)$/, 27 | exclude: /node_modules/, 28 | type: "asset/resource", 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: "html-loader", 33 | }, 34 | { 35 | test: /\.css$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "style-loader", 40 | }, 41 | { 42 | loader: "css-loader", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 50 | new HtmlWebpackPlugin({ 51 | filename: "index.html", //Name of file in ./dist/ 52 | template: "index.html", //Name of template in ./src 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lemoncode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | [español](https://lemoncode.net/) 3 | 4 | 5 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/Readme_es.md) 6 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/Readme.md) 7 | 8 |
9 |
10 | 11 | 12 | # React Hooks By Example 13 |

14 | hooks 15 |

16 | Set of step by step guide examples covering React Hooks, from start to advanced cases. 17 | 18 | About this examples: 19 | 20 | - Each example is focused on a topic (simple and straightforward). 21 | - Each example contains a Readme.md with a step by step guide to reproduce it. 22 | 23 | # Examples implemented 24 | 25 | List of examples: 26 | 27 | - [00-boiler-plate](https://github.com/Lemoncode/react-hooks-by-example/tree/master/00-boilerplate): starting point, just a blank create-react-app project (all examples will take 28 | this as starting point). 29 | - [01-use-state](https://github.com/Lemoncode/react-hooks-by-example/tree/master/01-use-state): adding state (simple element) to a functional component. 30 | - [02-use-state-object](https://github.com/Lemoncode/react-hooks-by-example/tree/master/02-use-state-object): adding state (object) to a functional component. 31 | - [03-component-did-mount](https://github.com/Lemoncode/react-hooks-by-example/tree/master/03-component-did-onload): executing some operations when a functional component gets mounted. 32 | - [04-component-unmount](https://github.com/Lemoncode/react-hooks-by-example/tree/master/04-component_unmount): executing cleanup code when a functional component gets unmounted. 33 | - [05-mount-did-update](https://github.com/Lemoncode/react-hooks-by-example/tree/master/05-component-update-render): hooking to mount and component update events. 34 | - [06-ajax-field-change](https://github.com/Lemoncode/react-hooks-by-example/tree/master/06-ajax-field-change): triggering an ajax call whenever a given field gets updated. 35 | - [07-custom-hooks](https://github.com/Lemoncode/react-hooks-by-example/tree/master/07-custom-hook): creating our custom hook, great to simplify components and get reusable assets. 36 | - [08-pure-component](https://github.com/Lemoncode/react-hooks-by-example/tree/master/08-pure-component): creating pure functional components. 37 | - [09-pure-component-callback](https://github.com/Lemoncode/react-hooks-by-example/tree/master/09-pure-component-callback): creating pure functional components, that include function properties 38 | in their props. 39 | - [10-use-reducer](https://github.com/Lemoncode/react-hooks-by-example/tree/master/10-use-reducer): _useReducer_ effect, including dispatch. 40 | - [11-use-context](https://github.com/Lemoncode/react-hooks-by-example/tree/master/11-use-context): using the _useContext_ hook to get access to the context in one line of code. 41 | - [12-set-state-func](https://github.com/Lemoncode/react-hooks-by-example/tree/master/12-set-state-func): Whe calling _setState_ how to ensure we are 42 | using the latest state value. 43 | - [13-async-closure](https://github.com/Lemoncode/react-hooks-by-example/tree/master/13-async-closure): advanced case, getting fresh data from _useState_ on callbacks. 44 | - [14-useref-dom](https://github.com/Lemoncode/react-hooks-by-example/tree/master/14-use-ref-dom): using _useRef_ hook to access a DOM element child. 45 | - [15-promise-unmounted](https://github.com/Lemoncode/react-hooks-by-example/tree/master/15-promise-unmounted): tracking when component is mounted/unmounted to avoid perform a state update on an unmounted component. 46 | - [16-memo-predicate](https://github.com/Lemoncode/react-hooks-by-example/tree/master/16-memo-predicate): enhancing rendering performance hooking to 'shouldComponentUpdate'. 47 | - [17-use-debug-value](https://github.com/Lemoncode/react-hooks-by-example/tree/master/17-use-debug-value): using built-in hook _useDebugValue_. 48 | - [18-why-did-you-update](https://github.com/Lemoncode/react-hooks-by-example/tree/master/18-why-did-you-update): implementing a custom hook to avoid unnecesary re-renders. 49 | 50 | # About Basefactor + Lemoncode 51 | 52 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 53 | 54 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 55 | 56 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 57 | 58 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 59 | -------------------------------------------------------------------------------- /Readme_es.md: -------------------------------------------------------------------------------- 1 | 2 | [español](https://lemoncode.net/) 3 | 4 | 5 | [english](https://github.com/Lemoncode/react-hooks-by-example/blob/master/Readme_es.md) 6 | [inglés](https://github.com/Lemoncode/react-hooks-by-example/blob/master/Readme.md) 7 | 8 |
9 |
10 | 11 | 12 | # React Hooks By Example 13 | 14 |

15 | hooks 16 |

17 | 18 | Conjunto de ejemplos paso a paso sobre React Hooks, desde cero hasta casos avanzados. 19 | 20 | Cada ejemplo: 21 | 22 | - Se centra en un tema (simple y directo). 23 | - Contiene un Readme_es.md con una guía paso por paso para reproducirlo. 24 | 25 | # Ejemplos implementados 26 | 27 | Listado de ejemplos: 28 | 29 | - [00-boiler-plate](https://github.com/Lemoncode/react-hooks-by-example/tree/master/00-boilerplate): punto de partida, simplemente crea un proyecto vacío con create-react-app (todos los ejemplos tomarán este ejemplo como punto de partida). 30 | - [01-use-state](https://github.com/Lemoncode/react-hooks-by-example/tree/master/01-use-state): añadiendo estado (elemento simple) a un componente funcional. 31 | - [02-use-state-object](https://github.com/Lemoncode/react-hooks-by-example/tree/master/02-use-state-object): añadiendo estado (objeto) a un componente funcional. 32 | - [03-component-did-mount](https://github.com/Lemoncode/react-hooks-by-example/tree/master/03-component-did-onload): ejecutando algunas operaciones cuando un componente funcional se monta. 33 | - [04-component-unmount](https://github.com/Lemoncode/react-hooks-by-example/tree/master/04-component_unmount): ejecutando código de cleanup cuando un componente funcional se desmonta. 34 | - [05-mount-did-update](https://github.com/Lemoncode/react-hooks-by-example/tree/master/05-component-update-render): hookeando los eventos de montaje y actualización de componentes. 35 | - [06-ajax-field-change](https://github.com/Lemoncode/react-hooks-by-example/tree/master/06-ajax-field-change): lanzando una llamada ajax siempre que un determinado campo se actualiza. 36 | - [07-custom-hooks](https://github.com/Lemoncode/react-hooks-by-example/tree/master/07-custom-hook): creando nuestro propio hook, genial para simplificar componentes y obtener recursos reutilizables. 37 | - [08-pure-component](https://github.com/Lemoncode/react-hooks-by-example/tree/master/08-pure-component): creando componentes funcionales puros. 38 | - [09-pure-component-callback](https://github.com/Lemoncode/react-hooks-by-example/tree/master/09-pure-component-callback): creando componentes funcionales puros que incluyen propiedas de funciones en sus props. 39 | - [10-use-reducer](https://github.com/Lemoncode/react-hooks-by-example/tree/master/10-use-reducer): usando el hook _useReducer_. 40 | - [11-use-context](https://github.com/Lemoncode/react-hooks-by-example/tree/master/11-use-context): usando el hook _useContext_ para acceder al contexto en una línea de código. 41 | - [12-set-state-func](https://github.com/Lemoncode/react-hooks-by-example/tree/master/12-set-state-func): como actualizar con setState asegurándonos 42 | que estamos usando el valor más actualizado del mismo. 43 | - [13-async-closure](https://github.com/Lemoncode/react-hooks-by-example/tree/master/13-async-closure): caso avanzado, obtener información reciente de _useState_ en los callbacks. 44 | - [14-useref-dom](https://github.com/Lemoncode/react-hooks-by-example/tree/master/14-use-ref-dom): usando _useRef_ para acceder a un elemento hijo del DOM. 45 | - [15-promise-unmounted](https://github.com/Lemoncode/react-hooks-by-example/tree/master/15-promise-unmounted): detectando si el componente está montado/desmontado para evitar realizar la actualización del estado de un componente no montado. 46 | - [16-memo-predicate](https://github.com/Lemoncode/react-hooks-by-example/tree/master/16-memo-predicate): mejorando el rendimiento del renderizado hookeando 'shouldComponentUpdate'. 47 | - [17-use-debug-value](https://github.com/Lemoncode/react-hooks-by-example/tree/master/17-use-debug-value): usando el hook interno _useDebugValue_. 48 | - [18-why-did-you-update](https://github.com/Lemoncode/react-hooks-by-example/tree/master/18-why-did-you-update): implementando un custom hook para evitar renderizados innecesarios. 49 | 50 | # ¿Te apuntas a nuestro máster? 51 | 52 | Si te ha gustado este ejemplo y tienes ganas de aprender Front End 53 | guiado por un grupo de profesionales ¿Por qué no te apuntas a 54 | nuestro [Máster Front End Online Lemoncode](https://lemoncode.net/master-frontend#inicio-banner)? Tenemos tanto edición de convocatoria 55 | con clases en vivo, como edición continua con mentorización, para 56 | que puedas ir a tu ritmo y aprender mucho. 57 | 58 | Y si tienes ganas de meterte una zambullida en el mundo _devops_ 59 | apuntate nuestro [Bootcamp devops online Lemoncode](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio) 60 | --------------------------------------------------------------------------------