Ganchos de reacción útiles que puede usar en sus proyectos
Publicado: 2022-03-10Los ganchos son simplemente funciones que le permiten conectarse o hacer uso de las funciones de React. Se presentaron en React Conf 2018 para abordar tres problemas principales de los componentes de clase: el infierno de los contenedores, los componentes enormes y las clases confusas. Los ganchos dan poder a los componentes funcionales de React, lo que hace posible desarrollar una aplicación completa con él.
Los problemas antes mencionados de componentes de clase están conectados y resolver uno sin el otro podría generar más problemas. Afortunadamente, los ganchos resolvieron todos los problemas de manera simple y eficiente mientras creaban espacio para características más interesantes en React. Los ganchos no reemplazan los conceptos y clases de React ya existentes, simplemente proporcionan una API para acceder a ellos directamente.
El equipo de React introdujo varios ganchos en React 16.8. Sin embargo, también puede usar ganchos de proveedores externos en su aplicación o incluso crear un gancho personalizado. En este tutorial, veremos algunos ganchos útiles en React y cómo usarlos. Revisaremos varios ejemplos de código de cada enlace y también exploraremos cómo crearía un enlace personalizado.
Nota: este tutorial requiere una comprensión básica de Javascript (ES6+) y React.
Motivación detrás de los ganchos
Como se indicó anteriormente, los ganchos se crearon para resolver tres problemas: el infierno de los envoltorios, los componentes enormes y las clases confusas. Echemos un vistazo a cada uno de estos con más detalle.
Infierno de envoltura
Las aplicaciones complejas creadas con componentes de clase se ejecutan fácilmente en el infierno de los contenedores. Si examina la aplicación en React Dev Tools, notará componentes profundamente anidados. Esto hace que sea muy difícil trabajar con los componentes o depurarlos. Si bien estos problemas podrían resolverse con componentes de orden superior y accesorios de representación , requieren que modifique un poco su código. Esto podría generar confusión en una aplicación compleja.
Los ganchos son fáciles de compartir, no tiene que modificar sus componentes antes de reutilizar la lógica.
Un buen ejemplo de esto es el uso de Redux connect
Higher Order Component (HOC) para suscribirse a la tienda Redux. Como todos los HOC, para usar Connect HOC, debe exportar el componente junto con las funciones de orden superior definidas. En el caso de connect
, tendremos algo de esta forma.
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
Donde mapStateToProps
y mapDispatchToProps
son funciones por definir.
Mientras que en la era de los ganchos, uno puede lograr fácilmente el mismo resultado de manera clara y sucinta usando los ganchos useSelector
y useDispatch
de Redux.
Componentes enormes
Los componentes de clase suelen contener efectos secundarios y lógica con estado. A medida que la aplicación crece en complejidad, es común que el componente se vuelva desordenado y confuso. Esto se debe a que se espera que los efectos secundarios estén organizados por métodos de ciclo de vida en lugar de por funcionalidad. Si bien es posible dividir los componentes y hacerlos más simples, esto a menudo introduce un mayor nivel de abstracción.
Los ganchos organizan los efectos secundarios por funcionalidad y es posible dividir un componente en partes según la funcionalidad.
clases confusas
Las clases son generalmente un concepto más difícil que las funciones. Los componentes basados en clases de React son detallados y un poco difíciles para los principiantes. Si es nuevo en Javascript, podría encontrar funciones más fáciles de usar debido a su sintaxis liviana en comparación con las clases. La sintaxis podría ser confusa; a veces, es posible olvidar vincular un controlador de eventos que podría romper el código.
React resuelve este problema con componentes y ganchos funcionales, lo que permite a los desarrolladores centrarse en el proyecto en lugar de en la sintaxis del código.
Por ejemplo, los siguientes dos componentes de React producirán exactamente el mismo resultado.
import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }
El primer ejemplo es un componente basado en clases, mientras que el segundo es un componente funcional. Aunque este es un ejemplo simple, observe cuán falso es el primer ejemplo en comparación con el segundo.
La convención y las reglas de Hooks
Antes de profundizar en los diversos ganchos, podría ser útil echar un vistazo a la convención y las reglas que se aplican a ellos. Estas son algunas de las reglas que se aplican a los ganchos.
- La convención de nomenclatura de los ganchos debe comenzar con el prefijo
use
. Entonces, podemos teneruseState
,useEffect
, etc. Si está utilizando editores de código modernos como Atom y VSCode, el complemento ESLint podría ser una característica muy útil para los ganchos de React. El complemento proporciona advertencias útiles y sugerencias sobre las mejores prácticas. - Los ganchos deben llamarse en el nivel superior de un componente, antes de la declaración de devolución. No se pueden llamar dentro de una declaración condicional, bucle o funciones anidadas.
- Los ganchos deben llamarse desde una función React (dentro de un componente React u otro gancho). No debe llamarse desde una función Vanilla JS.
El gancho useState
El gancho useState
es el gancho React más básico y útil. Al igual que otros ganchos integrados, este gancho debe importarse desde react
para usarse en nuestra aplicación.
import {useState} from 'react'
Para inicializar el estado, debemos declarar tanto el estado como su función de actualización y pasar un valor inicial.
const [state, updaterFn] = useState('')
Somos libres de llamar a nuestro estado y función de actualización como queramos, pero por convención, el primer elemento de la matriz será nuestro estado, mientras que el segundo elemento será la función de actualización. Es una práctica común prefijar nuestra función de actualización con el conjunto de prefijos seguido del nombre de nuestro estado en forma de mayúsculas y minúsculas.
Por ejemplo, establezcamos un estado para contener valores de conteo.
const [count, setCount] = useState(0)
Observe que el valor inicial de nuestro estado de count
se establece en 0
y no en una cadena vacía. En otras palabras, podemos inicializar nuestro estado en cualquier tipo de variable de JavaScript, a saber, número, cadena, booleano, matriz, objeto e incluso BigInt. Existe una clara diferencia entre establecer estados con el useState
y los estados de componentes basados en clases. Cabe destacar que el useState
devuelve una matriz, también conocida como variables de estado y, en el ejemplo anterior, desestructuramos la matriz en state
y la función de updater
.
Representación de componentes
Establecer estados con el useState
hace que el componente correspondiente se vuelva a representar. Sin embargo, esto solo sucede si React detecta una diferencia entre el estado anterior o anterior y el estado nuevo. React hace la comparación de estado usando el algoritmo Javascript Object.is
.
Establecer estados con useState
Nuestro estado de count
se puede establecer en nuevos valores de estado simplemente pasando el nuevo valor a la función de actualización setCount
de la siguiente manera setCount(newValue)
.
Este método funciona cuando no queremos hacer referencia al valor de estado anterior. Si deseamos hacer eso, necesitamos pasar una función a la función setCount
.
Suponiendo que queremos agregar 5 a nuestra variable de count
cada vez que se hace clic en un botón, podríamos hacer lo siguiente.
import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample
En el código anterior, primero importamos el useState
de react
y luego inicializamos el estado de count
con un valor predeterminado de 0. Creamos un controlador onClick
para incrementar el valor de count
en 5 cada vez que se hace clic en el botón. Luego mostramos el resultado en una etiqueta h1
.
Configuración de matrices y estados de objetos
Los estados de las matrices y los objetos se pueden configurar de la misma manera que otros tipos de datos. Sin embargo, si deseamos conservar los valores ya existentes, debemos usar el operador de propagación ES6 al configurar los estados.
El operador de propagación en Javascript se usa para crear un nuevo objeto a partir de un objeto ya existente. Esto es útil aquí porque React
compara los estados con la operación Object.is
y luego los vuelve a representar en consecuencia.
Consideremos el siguiente código para establecer estados al hacer clic en el botón.
import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample
En el código anterior, creamos dos estados arr
y obj
y los inicializamos en algunos valores de matriz y objeto respectivamente. Luego creamos controladores onClick
llamados handleArrClick
y handleObjClick
para establecer los estados de la matriz y el objeto respectivamente. Cuando se dispara handleArrClick
, llamamos a setArr
y usamos el operador de propagación ES6 para distribuir los valores de matriz ya existentes y agregarle newArr
.
Hicimos lo mismo para el controlador handleObjClick
. Aquí llamamos a setObj
, extendimos los valores de los objetos existentes usando el operador de distribución ES6 y actualizamos los valores de name
y age
.
Naturaleza asíncrona del estado de useState
Como ya hemos visto, establecemos estados con useState
pasando un nuevo valor a la función de actualización. Si se llama al actualizador varias veces, los nuevos valores se agregarán a una cola y la nueva representación se realizará en consecuencia mediante la comparación JavaScript Object.is
.
Los estados se actualizan de forma asíncrona. Esto significa que el nuevo estado primero se agrega a un estado pendiente y, posteriormente, el estado se actualiza. Por lo tanto, aún puede obtener el valor del estado anterior si accede al estado inmediatamente cuando se establece.
Consideremos el siguiente ejemplo para observar este comportamiento.
En el código anterior, creamos un estado de count
usando el gancho useState
. Luego creamos un controlador onClick
para incrementar el estado de count
cada vez que se hace clic en el botón. Observe que aunque el estado de count
aumentó, como se muestra en la etiqueta h2
, el estado anterior aún se registra en la consola. Esto se debe a la naturaleza asíncrona del enlace.
Si deseamos obtener el nuevo estado, podemos manejarlo de manera similar a como manejaríamos las funciones asíncronas. Esta es una forma de hacerlo.
Aquí, almacenamos newCountValue
creado para almacenar el valor de conteo actualizado y luego establecemos el estado de count
con el valor actualizado. Luego, registramos el valor de conteo actualizado en la consola.
El gancho useEffect
useEffect
es otro gancho React importante que se usa en la mayoría de los proyectos. Hace algo similar a los métodos de ciclo de vida del componentDidMount
, componentWillUnmount
y componentDidUpdate
del componente basado en clase. useEffect
nos brinda la oportunidad de escribir códigos imperativos que pueden tener efectos secundarios en la aplicación. Ejemplos de tales efectos incluyen registros, suscripciones, mutaciones, etc.
El usuario puede decidir cuándo se ejecutará useEffect
; sin embargo, si no está configurado, los efectos secundarios se ejecutarán en cada representación o nueva representación.
Considere el siguiente ejemplo.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }
En el código anterior, simplemente registramos el count
en useEffect
. Esto se ejecutará después de cada renderizado del componente.
A veces, es posible que queramos ejecutar el gancho una vez (en el montaje) en nuestro componente. Podemos lograr esto proporcionando un segundo parámetro para useEffect
hook.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }
El gancho useEffect
tiene dos parámetros, el primer parámetro es la función que queremos ejecutar, mientras que el segundo parámetro es una matriz de dependencias. Si no se proporciona el segundo parámetro, el gancho se ejecutará continuamente.
Al pasar un corchete vacío al segundo parámetro del gancho, le indicamos a React que ejecute el gancho useEffect
solo una vez, en la montura. Esto mostrará el valor 1
en la etiqueta h1
porque el recuento se actualizará una vez, de 0 a 1, cuando se monte el componente.
También podríamos hacer que nuestro efecto secundario se ejecute cada vez que cambien algunos valores dependientes. Esto se puede hacer pasando estos valores en la lista de dependencias.
Por ejemplo, podríamos hacer que useEffect
se ejecute siempre que el count
cambie de la siguiente manera.
import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;
El useEffect
anterior se ejecutará cuando se cumpla cualquiera de estas dos condiciones.
- En el montaje: después de renderizar el componente.
- Cuando cambia el valor de
count
.
En el montaje, la expresión console.log
se ejecutará y registrará el count
en 0. Una vez que se actualice el count
, se cumple la segunda condición, por lo que useEffect
se ejecuta nuevamente, esto continuará cada vez que se haga clic en el botón.
Una vez que proporcionemos el segundo argumento a useEffect
, se espera que le pasemos todas las dependencias. Si tiene ESLINT
instalado, mostrará un error de pelusa si alguna dependencia no se pasa a la lista de parámetros. Esto también podría hacer que el efecto secundario se comporte de forma inesperada, especialmente si depende de los parámetros que no se pasan.
Limpiando el efecto
useEffect
también nos permite limpiar los recursos antes de que se desmonte el componente. Esto puede ser necesario para evitar pérdidas de memoria y hacer que la aplicación sea más eficiente. Para hacer esto, devolveríamos la función de limpieza al final del gancho.
useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })
El useEffect
anterior se registrará mounted
cuando se monte el componente. Desmontando... la limpieza aquí se registrará cuando se desmonte el componente. Esto puede suceder cuando el componente se elimina de la interfaz de usuario.
El proceso de limpieza normalmente sigue el siguiente formulario.
useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })
Si bien es posible que no encuentre tantos casos de uso para las suscripciones useEffect
, es útil cuando se trata de suscripciones y temporizadores. En particular, cuando se trata de sockets web, es posible que deba darse de baja de la red para ahorrar recursos y mejorar el rendimiento cuando se desmonta el componente.
Obtener y recuperar datos con useEffect
Uno de los casos de uso más comunes del gancho useEffect
es obtener y obtener datos de una API.
Para ilustrar esto, usaremos datos de usuario falsos que creé a partir de JSONPlaceholder
para obtener datos con el gancho useEffect
.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }
En el código anterior, creamos un estado de users
usando el gancho useState
. Luego obtuvimos datos de una API usando Axios. Este es un proceso asincrónico, por lo que usamos la función async/await, también podríamos haber usado el punto y luego la sintaxis. Dado que obtuvimos una lista de usuarios, simplemente la mapeamos para mostrar los datos.
Observe que pasamos un parámetro vacío al gancho. Esto asegura que se llame solo una vez cuando se monte el componente.
También podemos recuperar los datos cuando cambian algunas condiciones. Lo mostraremos en el siguiente código.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }
Aquí creamos dos ganchos useEffect
. En el primero, usamos la sintaxis de punto y luego para obtener todos los usuarios de nuestra API. Esto es necesario para determinar el número de usuarios.
Luego creamos otro useEffect
para obtener un usuario basado en la id
. Este useEffect
recuperará los datos cada vez que cambie la identificación. Para garantizar esto, pasamos la id
en la lista de dependencias.
A continuación, creamos funciones para actualizar el valor de nuestra id
cada vez que se hace clic en los botones. Una vez que el valor de la id
cambia, useEffect
se ejecutará nuevamente y recuperará los datos.
Si queremos, incluso podemos limpiar o cancelar el token basado en promesas en Axios, podemos hacerlo con el método de limpieza mencionado anteriormente.
useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);
Aquí, pasamos el token de Axios como segundo parámetro a axios.get
. Cuando el componente se desmonta, cancelamos la suscripción llamando al método de cancelación del objeto de origen.
El gancho useReducer
El gancho useReducer
es un gancho React muy útil que hace algo similar al gancho useState
. De acuerdo con la documentación de React, este gancho debe usarse para manejar una lógica más compleja que el gancho useState
. Vale la pena señalar que el useState
se implementa internamente con el enlace useReducer.
El gancho toma un reductor como argumento y, opcionalmente, puede tomar el estado inicial y una función de inicio como argumentos.
const [state, dispatch] = useReducer(reducer, initialState, init)
Aquí, init
es una función y se usa cada vez que queremos crear el estado inicial de forma perezosa.
Veamos cómo implementar el useReducer
mediante la creación de una aplicación sencilla de tareas como se muestra en el entorno limitado a continuación.
En primer lugar, debemos crear nuestro reductor para contener los estados.
export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;
Creamos tres constantes correspondientes a nuestros tipos de acción. Podríamos haber usado cadenas directamente, pero este método es preferible para evitar errores tipográficos.
Luego creamos nuestra función reductora. Como en Redux
, el reductor debe tomar el estado y el objeto de acción. Pero a diferencia de Redux, no necesitamos inicializar nuestro reductor aquí.
Además, para muchos casos de uso de administración de estado, un useReducer
junto con el dispatch
expuesto a través del contexto puede permitir que una aplicación más grande active acciones, actualice state
y lo escuche.
Luego usamos las declaraciones de switch
para verificar el tipo de acción pasada por el usuario. Si el tipo de acción es ADD_TODO
, queremos pasar una nueva tarea pendiente y si es REMOVE_TODO
, queremos filtrar las tareas pendientes y eliminar la que corresponde a la id
pasada por el usuario. Si es COMPLETE_TODO
, queremos mapear las tareas pendientes y alternar la que tiene la id
pasada por el usuario.
Aquí está el archivo App.js
donde implementamos el reducer
.
import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }
Aquí, creamos un formulario que contiene un elemento de entrada para recopilar la entrada del usuario y un botón para activar la acción. Cuando se envía el formulario, enviamos una acción de tipo ADD_TODO
, pasando una nueva identificación y texto de tareas pendientes. Creamos una nueva identificación incrementando el valor de identificación anterior en 1. Luego borramos el cuadro de texto de entrada. Para eliminar y completar tareas pendientes, simplemente realizamos las acciones correspondientes. Estos ya se han implementado en el reductor como se muestra arriba.
Sin embargo, la magia sucede porque estamos usando el gancho useReducer
. Este enlace acepta el reductor y el estado inicial y devuelve el estado y la función de envío. Aquí, la función dispatch tiene el mismo propósito que la función setter para el hook useState
y podemos llamarlo como queramos en lugar de dispatch
.
Para mostrar los elementos de tareas pendientes, simplemente mapeamos la lista de tareas pendientes devuelta en nuestro objeto de estado como se muestra en el código anterior.
Esto muestra el poder del gancho useReducer
. También podríamos lograr esta funcionalidad con el useState
pero, como puede ver en el ejemplo anterior, el useReducer
nos ayudó a mantener las cosas más ordenadas. useReducer
a menudo es beneficioso cuando el objeto de estado es una estructura compleja y se actualiza de diferentes maneras en comparación con un simple reemplazo de valor. Además, una vez que estas funciones de actualización se vuelven más complicadas, useReducer
facilita mantener toda esa complejidad en una función de reducción (que es una función JS pura), lo que hace que sea muy fácil escribir pruebas solo para la función de reducción.
También podríamos haber pasado el tercer argumento al gancho useReducer
para crear el estado inicial de forma perezosa. Esto significa que podríamos calcular el estado inicial en una función init
.
Por ejemplo, podríamos crear una función init
de la siguiente manera:
const initFunc = () => [ { id: id, text: "First Item", completed: false } ]
y luego pasarlo a nuestro hook useReducer
.
const [state, dispatch] = useReducer(reducer, initialState, initFunc)
Si hacemos esto, initFunc
anulará el estado initialState
que proporcionamos y el estado inicial se calculará de forma perezosa.
El gancho useContext
La API de contexto de React proporciona una forma de compartir estados o datos en todo el árbol de componentes de React. La API ha estado disponible en React, como una función experimental, durante un tiempo, pero se volvió seguro de usar en React 16.3.0. La API facilita el intercambio de datos entre componentes al tiempo que elimina la perforación de apoyo.
Si bien puede aplicar React Context a toda su aplicación, también es posible aplicarlo a parte de la aplicación.
Para usar el gancho, primero debe crear un contexto usando React.createContext
y este contexto luego se puede pasar al gancho.
Para demostrar el uso del useContext
, creemos una aplicación simple que aumentará el tamaño de fuente en toda nuestra aplicación.
Vamos a crear nuestro contexto en el archivo context.js
.
import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;
Aquí, creamos un contexto y le pasamos un valor inicial de 16
, y luego exportamos el contexto. A continuación, conectemos nuestro contexto a nuestra aplicación.
import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;
En el código anterior, envolvimos todo nuestro árbol de componentes con FontSizeContext.Provider
y pasamos el size
a su propiedad de valor. Aquí, el size
es un estado creado con el gancho useState
. Esto nos permite cambiar la propiedad de valor cada vez que cambia el estado del size
. Al envolver todo el componente con el Provider
, podemos acceder al contexto en cualquier lugar de nuestra aplicación.
Por ejemplo, accedimos al contexto en <PageOne />
y <PageTwo />
. Como resultado, el tamaño de fuente aumentará en estos dos componentes cuando lo aumentemos desde el archivo App.js
Podemos aumentar o disminuir el tamaño de fuente desde los botones como se muestra arriba y una vez que lo hacemos, el tamaño de fuente cambia a lo largo de la aplicación.
import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;
Aquí, accedimos al contexto usando el gancho useContext
de nuestro componente PageOne
. Luego usamos este contexto para establecer nuestra propiedad de tamaño de fuente. Se aplica un procedimiento similar al archivo PageTwo.js
.
Los temas u otras configuraciones de nivel de aplicación de orden superior son buenos candidatos para contextos.
Usando useContext
y useReducer
Cuando se usa con el gancho useReducer
, useContext
nos permite crear nuestro propio sistema de gestión de estado. Podemos crear estados globales y administrarlos fácilmente en nuestra aplicación.
Mejoremos nuestra aplicación de tareas pendientes utilizando la API de contexto.
Como de costumbre, necesitamos crear un todoContext
en el archivo todoContext.js
.
import { createContext } from "react"; const initialState = []; export default createContext(initialState);
Aquí creamos el contexto, pasando un valor inicial de una matriz vacía. Luego exportamos el contexto.
Refactoricemos nuestro archivo App.js
separando la lista de tareas pendientes y los elementos.
import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }
Aquí, envolvimos nuestro archivo App.js
con TodoContext.Provider
y luego le pasamos los valores de retorno de nuestro todoReducer
. Esto hace que el estado del reductor y la función de dispatch
sean accesibles a través de nuestra aplicación.
Luego separamos la pantalla de tareas pendientes en un componente TodoList
. Hicimos esto sin perforaciones de apoyo, gracias a la API de contexto. Echemos un vistazo al archivo TodoList.js
.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;
Usando la desestructuración de arreglos, podemos acceder al estado (dejando la función de despacho) desde el contexto usando el gancho useContext
. Luego podemos mapear a través del estado y mostrar los elementos pendientes. Todavía extrajimos esto en un componente Todo
. La función de mapa de ES6+ requiere que pasemos una clave única y, como necesitamos la tarea específica, también la pasamos.
Echemos un vistazo al componente Todo
.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;
Nuevamente usando la desestructuración de arreglos, accedimos a la función de envío desde el contexto. Esto nos permite definir la función completeTodo
y removeTodo
como ya se discutió en la sección useReducer
. Con todo
prop pasado de todoList.js
podemos mostrar un elemento de tarea pendiente. También podemos marcarlo como completado y eliminar la tarea como creamos conveniente.
También es posible anidar más de un proveedor de contexto en la raíz de nuestra aplicación. Esto significa que podemos usar más de un contexto para realizar diferentes funciones en una aplicación.
Para demostrar esto, agreguemos temas al ejemplo de tareas pendientes.
Esto es lo que construiremos.
Nuevamente, tenemos que crear themeContext
. Para hacer esto, cree un archivo themeContext.js
y agregue los siguientes códigos.
import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);
Aquí, creamos un contexto y pasamos colors.light
como el valor inicial. Definamos los colores con esta propiedad en el archivo colors.js
.
const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;
En el código anterior, creamos un objeto de colors
que contiene propiedades claras y oscuras. Cada propiedad tiene un color de backgroundColor
y un objeto de color
.
A continuación, creamos el themeReducer
para manejar los estados del tema.
import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;
Como todos los reductores, el themeReducer
toma el estado y la acción. Luego usa la instrucción switch
para determinar la acción actual. Si es de tipo LIGHT
, simplemente asignamos accesorios Colors.light
y si es de tipo DARK
, mostramos accesorios Colors.dark
. Podríamos haber hecho esto fácilmente con el useState
pero elegimos useReducer
para llevar el punto a casa.
Habiendo configurado el themeReducer
, podemos integrarlo en nuestro archivo App.js
import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }
En el código anterior, agregamos algunas cosas a nuestra aplicación de tareas pendientes ya existente. Comenzamos importando ThemeContext
, themeReducer
, ThemeToggler
y Colors
. We created a reducer using the useReducer
hook, passing the themeReducer
and an initial value of Colors.light
to it. This returned the themeState
and themeDispatch
to us.
We then nested our component with the provider function from the ThemeContext
, passing the themeState
and the dispatch
functions to it. We also added theme styles to it by spreading out the themeStates
. This works because the colors
object already defined properties similar to what the JSX styles will accept.
However, the actual theme toggling happens in the ThemeToggler
component. Echémosle un vistazo.
import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;
In this component, we used the useContext
hook to retrieve the values we passed to the ThemeContext.Provider
from our App.js
file. As shown above, these values include the ThemeState
, dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight
to determine the current theme. This allows us to easily change the button text depending on the current theme.
The useMemo
Hook
The useMemo
hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo
will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.
The hook can be used as follows:
const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])
Let's consider three cases of the useMemo
hook.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - When no dependency value is passed.
The hook will recompute the value.
Let's take a look at an example to demonstrate this concept.
In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.
The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.
This is shown in the sandbox below.
First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).
const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);
Usamos axios
y el método async/await
await en el primer useEffect
y luego el punto y luego la sintaxis en el segundo. Estos dos enfoques funcionan de la misma manera.
A continuación, usando los datos de los empleados que obtuvimos arriba, calculemos las variables de alivio:
const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);
Este es un cálculo bastante complejo, por lo que tuvimos que envolverlo en un gancho useMemo
para memorizarlo u optimizarlo. Memorizarlo de esta manera garantizará que el cálculo no se vuelva a calcular si intentamos acceder al mismo empleado nuevamente.
Además, utilizando los valores de desgravación fiscal obtenidos anteriormente, nos gustaría calcular el PAYE y los ingresos después del PAYE.
const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);
Realizamos el cálculo de impuestos (un cálculo bastante complejo) utilizando las variables de impuestos calculadas anteriormente y luego las memorizamos con el gancho useMemo
.
El código completo está disponible aquí.
Esto sigue el procedimiento de cálculo de impuestos dado aquí. Primero calculamos la desgravación fiscal considerando los ingresos, el número de hijos y el número de familiares dependientes. Luego, multiplicamos la base imponible por las tasas del IRPF en pasos. Si bien el cálculo en cuestión no es completamente necesario para este tutorial, se proporciona para mostrarnos por qué useMemo
puede ser necesario. Este también es un cálculo bastante complejo, por lo que es posible que debamos memorizarlo con useMemo
como se muestra arriba.
Después de calcular los valores, simplemente mostramos el resultado.
Tenga en cuenta lo siguiente sobre el gancho useMemo
.
-
useMemo
debe usarse solo cuando sea necesario para optimizar el cálculo. En otras palabras, cuando el recálculo es costoso. - Es recomendable escribir primero el cálculo sin memorizarlo y solo memorizarlo si está causando problemas de rendimiento.
- El uso innecesario e irrelevante del
useMemo
puede incluso agravar los problemas de rendimiento. - A veces, demasiada memorización también puede causar problemas de rendimiento.
El gancho useCallback
useCallback
tiene el mismo propósito que useMemo
pero devuelve una devolución de llamada memorizada en lugar de un valor memorizado. En otras palabras, useCallback
es lo mismo que pasar useMemo
sin una llamada de función.
Por ejemplo, considere los siguientes códigos a continuación.
import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample
En el ejemplo anterior, tanto memoResult
como callbackResult
darán el mismo valor de 12
. Aquí, useCallback
devolverá un valor memorizado. Sin embargo, también podríamos hacer que devuelva una devolución de llamada memorizada pasándola como una función.
El useCallback
a continuación devolverá una devolución de llamada memorizada.
... const callbackResult = useCallback(() => a + b, [a, b]) ...
Luego podemos activar la devolución de llamada cuando se realiza una acción o en un useEffect
.
import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample
En el código anterior, definimos una función de devolución de llamada usando el gancho useCallback
. Luego llamamos a la devolución de llamada en un gancho useEffect
cuando se monta el componente y también cuando se hace clic en un botón.
Tanto el useEffect
como el clic del botón producen el mismo resultado.
Tenga en cuenta que los conceptos, lo que se debe y no se debe hacer que se aplican al gancho useMemo
también se aplican al gancho useCallback
. Podemos recrear el ejemplo de useMemo
con useCallback
.
El gancho useRef
useRef
devuelve un objeto que puede persistir en una aplicación. El gancho tiene solo una propiedad, current
, y podemos pasarle fácilmente un argumento.
Tiene el mismo propósito que un createRef
usado en componentes basados en clases. Podemos crear una referencia con este gancho de la siguiente manera:
const newRef = useRef('')
Aquí creamos una nueva referencia llamada newRef
y le pasamos una cadena vacía.
Este anzuelo se utiliza principalmente para dos fines:
- Acceder o manipular el DOM, y
- Almacenamiento de estados mutables: esto es útil cuando no queremos que el componente se vuelva a representar cuando cambia un valor.
Manipulando el DOM
Cuando se pasa a un elemento DOM, el objeto ref apunta a ese elemento y se puede usar para acceder a sus atributos y propiedades DOM.
Aquí hay un ejemplo muy simple para demostrar este concepto.
import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample
En el ejemplo anterior, definimos headingRef
usando el gancho useRef
pasando una cadena vacía. Luego establecemos la referencia en la etiqueta h1
pasando ref = {headingRef}
. Al establecer esta referencia, le hemos pedido a la referencia de headingRef
que apunte a nuestro elemento h1
. Esto significa que podemos acceder a las propiedades de nuestro elemento h1
desde la ref.
Para ver esto, si comprobamos el valor de console.log(headingRef)
, obtendremos {current: HTMLHeadingElement}
o {current: h1}
y podremos evaluar todas las propiedades o atributos del elemento. Algo similar se aplica a cualquier otro elemento HTML.
Por ejemplo, podríamos poner el texto en cursiva cuando se monta el componente.
useEffect(() => { headingRef.current.style.font; }, []);
Incluso podemos cambiar el texto a otra cosa.
... headingRef.current.innerHTML = "A Changed H1 Element"; ...
Incluso podemos cambiar el color de fondo del contenedor principal.
... headingRef.current.parentNode.style.backgroundColor = "red"; ...
Cualquier tipo de manipulación DOM se puede hacer aquí. Observe que headingRef.current
se puede leer de la misma manera que document.querySelector('.topheading')
.
Un caso de uso interesante del gancho useRef
en la manipulación del elemento DOM es enfocar el cursor en el elemento de entrada. Repasémoslo rápidamente.
import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample
En el código anterior, creamos inputRef
usando el useRef
y luego le pedimos que apuntara al elemento de entrada. Luego hicimos que el cursor se enfocara en la referencia de entrada cuando se carga el componente y cuando se hace clic en el botón usando inputRef.current.focus()
. Esto es posible porque focus()
es un atributo de los elementos de entrada y, por lo tanto, el árbitro podrá evaluar los métodos.
Las referencias creadas en un componente principal se pueden evaluar en el componente secundario reenviándolas mediante React.forwardRef()
. Echémosle un vistazo.
Primero vamos a crear otro componente NewInput.js
y agregarle los siguientes códigos.
import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;
Este componente acepta props
y ref
. Pasamos el ref a su prop de referencia y props.val
a su prop de marcador de posición. Los componentes regulares de React no toman un atributo ref
. Este atributo está disponible solo cuando lo envolvemos con React.forwardRef
como se muestra arriba.
Entonces podemos llamar fácilmente a esto en el componente principal.
... <NewInput val="Just an example" ref={inputRef} /> ...
Almacenamiento de los estados mutables
Las referencias no solo se usan para manipular elementos DOM, sino que también se pueden usar para almacenar valores mutables sin volver a renderizar todo el componente.
El siguiente ejemplo detectará la cantidad de veces que se hace clic en un botón sin volver a renderizar el componente.
import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }
En el código anterior, incrementamos countRef
cuando se hace clic en el botón y luego lo registramos en la consola. Aunque el valor se incrementa como se muestra en la consola, no podremos ver ningún cambio si tratamos de evaluarlo directamente en nuestro componente. Solo se actualizará en el componente cuando se vuelva a renderizar.
Tenga en cuenta que mientras useState
es asíncrono, useRef
es síncrono. En otras palabras, el valor está disponible inmediatamente después de su actualización.
El gancho useLayoutEffect
Al igual que el useEffect
, useLayoutEffect
se llama después de montar y renderizar el componente. Este gancho se dispara después de la mutación DOM y lo hace de forma sincrónica. Además de ser llamado sincrónicamente después de la mutación DOM, useLayoutEffect
hace lo mismo que useEffect
.
useLayoutEffect
solo debe usarse para realizar una mutación DOM o una medición relacionada con DOM; de lo contrario, debe usar el useEffect
. El uso del useEffect
para las funciones de mutación DOM puede causar algunos problemas de rendimiento, como parpadeo, pero useLayoutEffect
los maneja perfectamente, ya que se ejecuta después de que se hayan producido las mutaciones.
Echemos un vistazo a algunos ejemplos para demostrar este concepto.
- Obtendremos el ancho y el alto de la ventana al cambiar el tamaño.
import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample
En el código anterior, creamos un tamaño de windowSize
de estado con propiedades de ancho y alto. Luego, establecemos el estado en el ancho y alto de la ventana actual, respectivamente, cuando se cambia el tamaño de la ventana. También limpiamos el código cuando se desmonta. El proceso de limpieza es esencial en useLayoutEffect
para limpiar la manipulación del DOM y mejorar la eficiencia.
- Difuminemos un texto con
useLayoutEffect
.
import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }
Usamos useRef
y useLayoutEffect
juntos en el código anterior. Primero creamos una referencia, paragraphRef
para apuntar a nuestro párrafo. Luego, creamos un detector de eventos al hacer clic para monitorear cuando se hace clic en el párrafo y luego lo desdibujamos usando las propiedades de estilo que definimos. Finalmente, limpiamos el detector de eventos usando removeEventListener
.
Los useDispatch
y useSelector
useDispatch
es un gancho de Redux para enviar (activar) acciones en una aplicación. Toma un objeto de acción como argumento e invoca la acción. useDispatch
es la equivalencia del gancho a mapDispatchToProps
.
Por otro lado, useSelector
es un gancho de Redux para evaluar los estados de Redux. Se necesita una función para seleccionar el reductor Redux exacto de la tienda y luego devuelve los estados correspondientes.
Una vez que nuestra tienda Redux está conectada a una aplicación React a través del proveedor Redux, podemos invocar las acciones con useDispatch
y acceder a los estados con useSelector
. Cada acción y estado de Redux se puede evaluar con estos dos ganchos.
Tenga en cuenta que estos estados se envían con React Redux (un paquete que facilita la evaluación de la tienda Redux en una aplicación React). No están disponibles en la biblioteca principal de Redux.
Estos ganchos son muy simples de usar. Primero, tenemos que declarar la función de envío y luego activarla.
import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample
En el código anterior, useDispatch
y useSelector
de react-redux
. Luego, en un gancho useEffect
, despachamos la acción. Podríamos definir la acción en otro archivo y luego llamarla aquí o podríamos definirla directamente como se muestra en la llamada useEffect
.
Una vez que hayamos despachado las acciones, nuestros estados estarán disponibles. Luego podemos recuperar el estado usando el gancho useSelector
como se muestra. Los estados se pueden usar de la misma manera que usaríamos los estados del gancho useState
.
Echemos un vistazo a un ejemplo para demostrar estos dos ganchos.
Para demostrar este concepto, tenemos que crear una tienda Redux, un reductor y acciones. Para simplificar las cosas aquí, usaremos la biblioteca Redux Toolkit con nuestra base de datos falsa de JSONPlaceholder.
Necesitamos instalar los siguientes paquetes para comenzar. Ejecute los siguientes comandos bash.
npm i redux @reduxjs/toolkit react-redux axios
Primero, creemos el employeesSlice.js
para manejar el reductor y la acción para la API de nuestros empleados.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;
Esta es la configuración estándar para el kit de herramientas de Redux. Usamos createAsyncThunk
para acceder al middleware Thunk
para realizar acciones asincrónicas. Esto nos permitió obtener la lista de empleados de la API. Luego creamos el segmento de employeesSlice
y devolvimos, "cargando", "error" y los datos de los empleados según los tipos de acción.
El kit de herramientas Redux también facilita la configuración de la tienda. Aquí está la tienda.
import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;
Aquí, usamos combineReducers
para agrupar los reductores y la función configureStore
proporcionada por el kit de herramientas de Redux para configurar la tienda.
Procedamos a usar esto en nuestra aplicación.
Primero, necesitamos conectar Redux a nuestra aplicación React. Idealmente, esto debería hacerse en la raíz de nuestra aplicación. Me gusta hacerlo en el archivo index.js
.
import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );
Aquí, he importado la tienda que creé arriba y también el Provider
de react-redux
.
Luego, envolví toda la aplicación con la función Provider
, pasándole la tienda. Esto hace que la tienda sea accesible a través de nuestra aplicación.
Luego podemos proceder a usar los ganchos useDispatch
y useSelector
para obtener los datos.
Hagamos esto en nuestro archivo App.js
import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }
En el código anterior, usamos el gancho useDispatch
para invocar la acción fetchEmployees
creada en el archivo employeesSlice.js
. Esto hace que el estado de los empleados esté disponible en nuestra aplicación. Luego, usamos el gancho useSelector
para obtener los estados. A partir de entonces, mostramos los resultados mapeando a través de los employees
.
El gancho useHistory
La navegación es muy importante en una aplicación React. Si bien puede lograr esto de varias maneras, React Router proporciona una forma simple, eficiente y popular de lograr un enrutamiento dinámico en una aplicación React. Además, React Router proporciona un par de ganchos para evaluar el estado del enrutador y realizar la navegación en el navegador, pero para usarlos, primero debe configurar su aplicación correctamente.
Para usar cualquier enlace de React Router, primero debemos envolver nuestra aplicación con BrowserRouter
. Luego podemos anidar las rutas con Switch
y Route
.
Pero primero, tenemos que instalar el paquete ejecutando los siguientes comandos.
npm install react-router-dom
Luego, debemos configurar nuestra aplicación de la siguiente manera. Me gusta hacer esto en mi archivo App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }
Podríamos tener tantas Rutas como sea posible dependiendo de la cantidad de componentes que deseemos renderizar. Aquí, hemos renderizado solo el componente Employees
. El atributo de path
le dice a React Router DOM la ruta del componente y se puede evaluar con una cadena de consulta u otros métodos.
El orden importa aquí. La ruta raíz debe colocarse debajo de la ruta secundaria y así sucesivamente. Para anular este orden, debe incluir la palabra clave exact
en la ruta raíz.
<Route path='/' exact > <Employees /> </Route>
Ahora que hemos configurado el enrutador, podemos usar el useHistory
y otros enlaces React Router en nuestra aplicación.
Para usar el useHistory
, primero debemos declararlo de la siguiente manera.
import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }
Si registramos el historial en la consola, veremos varias propiedades asociadas con él. Estos incluyen block
, createHref
, go
, goBack
, goForward
, length
, listen
, location
, push
, replace
. Si bien todas estas propiedades son útiles, lo más probable es que utilice history.push
e history.replace
con más frecuencia que otras propiedades.
Usemos esta propiedad para pasar de una página a otra.
Suponiendo que queremos obtener datos sobre un empleado en particular cuando hacemos clic en sus nombres. Podemos usar el useHistory
para navegar a la nueva página donde se mostrará la información del empleado.
function moveToPage = (id) =>{ history.push(`/employees/${id}`) }
Podemos implementar esto en nuestro archivo Employee.js
agregando lo siguiente.
import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }
En la función pushToPage
, usamos el history
del useHistory
para navegar a la página del empleado y pasar la identificación del empleado al lado.
El gancho useLocation
Este gancho también se envía con React Router DOM. Es un gancho muy popular que se usa para trabajar con el parámetro de cadena de consulta. Este enlace es similar a window.location
en el navegador.
import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample
El useLocation
devuelve el nombre de la pathname
, el parámetro de search
, el hash
y state
. Los parámetros más comúnmente utilizados incluyen el nombre de la pathname
y la search
, pero igualmente podría usar hash
y state
mucho en su aplicación.
La propiedad location pathname
devolverá la ruta que establecimos en nuestra configuración de Route
. Mientras que la search
devolverá el parámetro de búsqueda de consulta, si lo hay. Por ejemplo, si pasamos 'http://mywebsite.com/employee/?id=1'
a nuestra consulta, la pathname
sería /employee
y la search
sería ?id=1
.
Luego podemos recuperar los diversos parámetros de búsqueda usando paquetes como query-string o codificándolos.
El gancho useParams
Si configuramos nuestra ruta con un parámetro de URL en su atributo de ruta, podemos evaluar esos parámetros como pares clave/valor con el gancho useParams
.
Por ejemplo, supongamos que tenemos la siguiente Ruta.
<Route path='/employees/:id' > <Employees /> </Route>
La ruta esperará una identificación dinámica en lugar de :id
.
Con el gancho useParams
, podemos evaluar la identificación pasada por el usuario, si la hay.
Por ejemplo, suponiendo que el usuario pasa lo siguiente en función con history.push
,
function goToPage = () => { history.push(`/employee/3`) }
Podemos usar el gancho useParams
para acceder a este parámetro de URL de la siguiente manera.
import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample
Si registramos params
en la consola, obtendremos el siguiente objeto {id: "3"}
.
El gancho useRouteMatch
Este enlace proporciona acceso al objeto de coincidencia. Devuelve la coincidencia más cercana a un componente si no se le proporciona ningún argumento.
El objeto de coincidencia devuelve varios parámetros, incluida la path
(la misma que la ruta especificada en Route), la URL
, el objeto params
e isExact
.
Por ejemplo, podemos usar useRouteMatch
para devolver componentes basados en la ruta.
import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;
En el código anterior, establecimos la ruta de una ruta con useRouteMatch
y luego representamos el componente <Employee />
o <Admin />
según la ruta seleccionada por el usuario.
Para que esto funcione, todavía necesitamos agregar la ruta a nuestro archivo App.js
... <Route> <CustomRoute /> </Route> ...
Construyendo un gancho personalizado
De acuerdo con la documentación de React, crear un enlace personalizado nos permite extraer una lógica en una función reutilizable. Sin embargo, debe asegurarse de que todas las reglas que se aplican a los ganchos de React se apliquen a su gancho personalizado. Verifique las reglas del gancho React en la parte superior de este tutorial y asegúrese de que su gancho personalizado cumpla con cada una de ellas.
Los ganchos personalizados nos permiten escribir funciones una vez y reutilizarlas cuando sea necesario y, por lo tanto, obedecer el principio DRY.
Por ejemplo, podríamos crear un enlace personalizado para obtener la posición de desplazamiento en nuestra página de la siguiente manera.
import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };
Aquí, definimos un enlace personalizado para determinar la posición de desplazamiento en una página. Para lograr esto, primero creamos un estado, scrollPos
, para almacenar la posición de desplazamiento. Dado que esto modificará el DOM, necesitamos usar useLayoutEffect
en lugar de useEffect
. Agregamos un detector de eventos de desplazamiento para capturar las posiciones de desplazamiento x e y y luego limpiamos el detector de eventos. Finalmente, volvimos a la posición de desplazamiento.
Podemos usar este enlace personalizado en cualquier lugar de nuestra aplicación llamándolo y usándolo como lo haríamos con cualquier otro estado.
import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App
Aquí, importamos el gancho personalizado useScrollPos
que creamos arriba. Luego lo inicializamos y luego registramos el valor en nuestra consola. Si nos desplazamos por la página, el gancho nos mostrará la posición de desplazamiento en cada paso del desplazamiento.
Podemos crear ganchos personalizados para hacer casi cualquier cosa que podamos imaginar en nuestra aplicación. Como puede ver, simplemente necesitamos usar el gancho React incorporado para realizar algunas funciones. También podemos usar bibliotecas de terceros para crear ganchos personalizados, pero si lo hacemos, tendremos que instalar esa biblioteca para poder usar el gancho.
Conclusión
En este tutorial, echamos un buen vistazo a algunos ganchos de React útiles que usará en la mayoría de sus aplicaciones. Examinamos lo que presentan y cómo usarlos en su aplicación. También analizamos varios ejemplos de código para ayudarlo a comprender estos ganchos y aplicarlos a su aplicación.
Te animo a que pruebes estos ganchos en tu propia aplicación para comprenderlos mejor.
Recursos de The React Docs
- Preguntas frecuentes sobre ganchos
- Kit de herramientas Redux
- Uso del gancho de estado
- Uso del gancho de efectos
- Referencia de la API de ganchos
- Ganchos React Redux
- Ganchos de enrutador React