b
Hooks personalizados
Hooks
React ofrece 15 hooks incorporados diferentes, de los cuales los más populares son useState y useEffect, a los cuales ya hemos estado utilizando extensivamente.
En la parte 5 usamos el hook useImperativeHandle que permite que los componentes proporcionen sus funciones a otros componentes. En la parte 6 utilizamos useReducer y useContext para implementar una gestión de estado similar a Redux.
Durante el último año, muchas librerías de React han comenzado a ofrecer APIs basadas en hooks. En la parte 6 usamos los hooks useSelector y useDispatch de la librería react-redux para compartir nuestra redux-store y la función dispatch a nuestros componentes.
La API de React-Router que presentamos en la parte anterior también se basa parcialmente en hooks. Sus hooks se pueden usar para acceder a los parámetros de la URL y al objeto navigation, lo que permite manipular la URL del navegador programáticamente.
Como se mencionó en la parte 1, los hooks no son funciones normales y cuando los usamos tenemos que cumplir con ciertas reglas o limitaciones. Recapitulemos las reglas del uso de hooks, copiadas literalmente de la documentación oficial de React:
Evita utilizar Hooks dentro de loops, condicionales o funciones anidadas. En su lugar, utiliza los Hooks únicamente en el nivel superior de tu función de React.
Los Hooks sólo deben ser utilizados durante la renderización de un componente de función en React:
- Utilízalos en el nivel superior del cuerpo de un componente de función.
- Utilízalos en el nivel superior del cuerpo de un Hook personalizado.
Existe un plugin de ESlint que se puede usar para verificar que la aplicación usa los hooks correctamente.
Hooks personalizados
React ofrece la opción de crear nuestros propios hooks personalizados. Según React, el propósito principal de los hooks personalizados es facilitar la reutilización de la lógica utilizada en los componentes.
Crear tus propios Hooks te permite extraer la lógica de los componentes en funciones reutilizables.
Los hooks personalizados son funciones regulares de JavaScript que pueden utilizar cualquier otro hook, siempre que se adhieran a las reglas de los hooks. Además, el nombre de los hooks personalizados debe comenzar con la palabra use.
Implementamos una aplicación de contador en la parte 1, que puede tener su valor incrementado, reducido o reiniciado. El código de la aplicación es el siguiente:
import { useState } from 'react'
const App = () => {
const [counter, setCounter] = useState(0)
return (
<div>
<div>{counter}</div>
<button onClick={() => setCounter(counter + 1)}>
plus
</button>
<button onClick={() => setCounter(counter - 1)}>
minus
</button>
<button onClick={() => setCounter(0)}>
zero
</button>
</div>
)
}
Extraigamos la lógica del contador en su propio hook personalizado. El código del hook es el siguiente:
const useCounter = () => {
const [value, setValue] = useState(0)
const increase = () => {
setValue(value + 1)
}
const decrease = () => {
setValue(value - 1)
}
const zero = () => {
setValue(0)
}
return {
value,
increase,
decrease,
zero
}
}
Nuestro hook personalizado utiliza el hook useState internamente para crear su propio estado. El hook devuelve un objeto, cuyas propiedades incluyen el valor del contador, así como funciones para manipular el valor.
Los componentes de React pueden utilizar el hook como se muestra a continuación:
const App = (props) => {
const counter = useCounter()
return (
<div>
<div>{counter.value}</div>
<button onClick={counter.increase}>
plus
</button>
<button onClick={counter.decrease}>
minus
</button>
<button onClick={counter.zero}>
zero
</button>
</div>
)
}
Al hacer esto, podemos extraer el estado del componente App y su manipulación por completo en el hook useCounter. La gestión del estado y la lógica del contador ahora es responsabilidad del hook personalizado.
El mismo hook podría reutilizarse en la aplicación que realizaba un seguimiento de la cantidad de clics realizados en los botones izquierdo y derecho:
const App = () => {
const left = useCounter()
const right = useCounter()
return (
<div>
{left.value}
<button onClick={left.increase}>
left
</button>
<button onClick={right.increase}>
right
</button>
{right.value}
</div>
)
}
La aplicación crea dos contadores completamente separados. El primero se asigna a la variable left y el otro a la variable right.
Tratar con formularios en React es algo complicado. La siguiente aplicación presenta al usuario un formulario que le solicita que ingrese su nombre, fecha de nacimiento y altura:
const App = () => {
const [name, setName] = useState('')
const [born, setBorn] = useState('')
const [height, setHeight] = useState('')
return (
<div>
<form>
name:
<input
type='text'
value={name}
onChange={(event) => setName(event.target.value)}
/>
<br/>
birthdate:
<input
type='date'
value={born}
onChange={(event) => setBorn(event.target.value)}
/>
<br />
height:
<input
type='number'
value={height}
onChange={(event) => setHeight(event.target.value)}
/>
</form>
<div>
{name} {born} {height}
</div>
</div>
)
}
Cada campo del formulario tiene su propio estado. Para mantener el estado del formulario sincronizado con los datos proporcionados por el usuario, tenemos que registrar un controlador onChange apropiado para cada uno de los elementos input.
Definamos nuestro propio hook personalizado useField, que simplifica la gestión del estado del formulario:
const useField = (type) => {
const [value, setValue] = useState('')
const onChange = (event) => {
setValue(event.target.value)
}
return {
type,
value,
onChange
}
}
La función de hook recibe el tipo de campo de entrada como parámetro. Devuelve todos los atributos requeridos por el input: su tipo, valor y el controlador onChange.
El hook se puede utilizar de la siguiente manera:
const App = () => {
const name = useField('text')
// ...
return (
<div>
<form>
<input
type={name.type}
value={name.value}
onChange={name.onChange}
/>
// ...
</form>
</div>
)
}
Propagando atributos con Spread
Podríamos simplificar un poco más las cosas. Dado que el objeto name tiene exactamente todos los atributos que el elemento input espera recibir como props, podemos pasar los props al elemento usando la sintaxis spread(spread syntax) de la siguiente manera:
<input {...name} />
Como indica el ejemplo en la documentación de React, las siguientes dos formas de pasar props a un componente logran exactamente el mismo resultado:
<Greeting firstName='Arto' lastName='Hellas' />
const person = {
firstName: 'Arto',
lastName: 'Hellas'
}
<Greeting {...person} />
La aplicación se simplifica en el siguiente formato:
const App = () => {
const name = useField('text')
const born = useField('date')
const height = useField('number')
return (
<div>
<form>
name:
<input {...name} />
<br/>
birthdate:
<input {...born} />
<br />
height:
<input {...height} />
</form>
<div>
{name.value} {born.value} {height.value}
</div>
</div>
)
}
Tratar con formularios se simplifica enormemente cuando los desagradables detalles esenciales relacionados con la sincronización del estado del formulario se encapsulan dentro de nuestro hook personalizado.
Los hooks personalizados no son solo una herramienta para reutilizar código; sino que también brindan una mejor manera de dividirlo en partes modulares más pequeñas.
Más sobre hooks
Internet está comenzando a llenarse con más y más material útil relacionado con los hooks. Vale la pena consultar las siguientes fuentes: