,
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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/03-component-did-onload/Readme_es.md)
5 | [](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 |
}
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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/04-component_unmount/Readme_es.md)
5 | [](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
}
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
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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/05-component-update-render/Readme_es.md)
5 | [](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 |
,
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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/06-ajax-field-change/Readme_es.md)
5 | [](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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/06-ajax-field-change/Readme_es.md)
5 | [](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 |
,
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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component/Readme_es.md)
5 | [](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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/08-pure-component/Readme_es.md)
5 | [](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
,
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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/09-pure-component-callback/Readme_es.md)
5 | [](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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/09-pure-component-callback/Readme_es.md)
5 | [](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 |
,
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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/12-set-state-func/Readme_es.md)
5 | [](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 | [](https://lemoncode.net/)
2 |
3 |
4 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/12-set-state-func/Readme_es.md)
5 | [](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 |
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 | 
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 | [](https://lemoncode.net/)
3 |
4 |
5 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/17-use-debug-value/Readme_es.md)
6 | [](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 |
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 | [](https://lemoncode.net/)
3 |
4 |
5 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/Readme_es.md)
6 | [](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 |
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 | [](https://lemoncode.net/)
3 |
4 |
5 | [](https://github.com/Lemoncode/react-hooks-by-example/blob/master/Readme_es.md)
6 | [](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 |
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 |
--------------------------------------------------------------------------------