hand Inicio

SERVICIOS

Salir

nochedía

DESARROLLO WEB con
GATSBY y WORDPRESS

emoji Cursos [24]
emoji Themes Gatsby [5]
emoji Themes WordPress [2]
emoji Blog [83]
emoji Herramientas [11]

Utilizando useReducer

500 palabras
2 minutos
July 3, 2020
blogjavascript

useReducer nos permite integrar una función en useState y así poder trabajar más cómodamente con estados complejos

  1. useReducer
  2. Por qué useReducer?
  3. Añadiendo complejidad al estado

useReducer

Una manera de definir useReducer es diciendo que es la versión completa de useState

Con useState podemos controlar el estado (es decir, guardar variables que persisten entre los distintos re-renders que ocurran en nuestra aplicación)

js
const [estado, cambiarEstado] = useState('')

La función useState acepta cualquier valor (excepto una función, pero vamos a olvidarnos de esto) que pasa a ser el valor inicial del estado

Y nos devuelve dos variables, el estado en sí y la función para canviar el estado

Ejemplos:

js
// inicializamos el hook
const [estado, setEstado] = useState({ status: 'pending', value: null })
// cambiamos el valor del estado
setEstado({ status: 'ok', value: 'whatever' })

Inciso 1

Cada vez que cambiamos el estado nuestra aplicación sufrirá un re-renderizado

Si queremos almacenar un estado sin forzar ese repintado, en lugar de useState utilizaremos el hook useRef

Inciso 2

Un error común es pensar que se ha cambiado el estado sin que sea verdad

js
const [estado, setEstado] = useState({ status: 'pending', value: null })
estado.status = 'ok'
setEstado(estado) // no hay repaint!

Aquí hemos cambiado el contenido del objeto pero NO el objeto, por lo que para React no hay razón para repintar nada

Para cambiarlo tendríamos que cambiar el objeto

js
const [estado, setEstado] = useState({ status: 'pending', value: null })
setEstado({ ...estado, status: 'ok' })

Por qué useReducer?

El nombre de useReduce ya nos da pistas de que esto tiene que ver con el método reduce()

[ tienes una entrada del Array.reduce aquí ]

Vamos con código

Pensando en la clásica aplicación ToDo, vamos a crear una lista de tareas que entraremos con un <input>

js
import React, { useRef, useState } from 'react'
export const App = () => {
// referencia para gestionar el input
const inputRef = useRef()
// el estado, con un valor inicial de []
const [elements, setElements] = useState([])
// funciones para añadir y quitar elementos del estado
const addElement = () => setElements([inputRef.current.value, ...elements])
const removeElement = i => setElements([...elements.slice(0, i), ...elements.slice(i + 1)])
// el jsx que gestiona la interfaz
return (
<div>
<input ref={inputRef} />
<button onClick={addElement}>✏️</button>
{elements.map((el, i) => (
<div key={'element' + i}>
<span>{el}</span>
<button onClick={() => removeElement(i)}>✔️</button>
</div>
))}
</div>
)
}
// el export default no es necesario si importamos `App` explícitamente (lo cual es recomendable)
// aquí lo añado aquí para que el codesandbox funcione out-of-the-box
export default App
Ver en CodeSandBox

Aquí tenemos un input para añadir entradas en la lista, y un botón para quitar entradas de la lista, y esa lista la guardamos como estado

Y ese estado es un array

Esto así es la mar de legible, pero voy a refactorizarlo para abstraer la lógica y tener un elemento en común

js
import React, { useRef, useState } from 'react'
export const App = () => {
const inputRef = useRef()
const [elements, setElements] = useState([])
// mi super nueva función
const changeElements = action =>
action.type === 'add'
? [action.value, ...elements]
: action.type === 'remove'
? [...elements.slice(0, action.i), ...elements.slice(action.i + 1)]
: []
const addElement = () => setElements(changeElements({ type: 'add', value: inputRef.current.value }))
const removeElement = i => setElements(changeElements({ type: 'remove', i: i }))
return (
<div>
<input ref={inputRef} />
<button onClick={addElement}>✏️</button>
{elements.map((el, i) => (
<div key={'element' + i}>
<span>{el}</span>
<button onClick={() => removeElement(i)}>✔️</button>
</div>
))}
</div>
)
}
export default App
Ver en CodeSandBox

Ahora tengo una función que me gestiona la lógica changeElements y dos funciones para añadir y quitar elementos más legibles

En general el código de ahora es más ofuscado, pero se entiende el concepto

Bien

Pues ahora vamos a reescribir lo mismo con useReducer

js
import React, { useRef, useReducer } from 'react'
export const App = () => {
const inputRef = useRef()
const [elements, setElements] = useReducer(
(state, action) =>
action.type === 'add'
? [action.value, ...state]
: action.type === 'remove'
? [...state.slice(0, action.i), ...state.slice(action.i + 1)]
: [],
[]
)
const addElement = () => setElements({ type: 'add', value: inputRef.current.value })
const removeElement = i => setElements({ type: 'remove', i: i })
return (
<div>
<input ref={inputRef} />
<button onClick={addElement}>✏️</button>
{elements.map((el, i) => (
<div key={'element' + i}>
<span>{el}</span>
<button onClick={() => removeElement(i)}>✔️</button>
</div>
))}
</div>
)
}
export default App
Ver en CodeSandBox

Es decir, lo que nos permite useReducer es integrar la función que antes teníamos fuera, dentro de la propia lógica del estado

El funcionamiento es el mismo que con reduce, recibimos primero el acumulador, que en este caso será nuestro estado, y después el nuevo valor del estado

Añadiendo complejidad al estado

Aquí se podría argumentar que el código inicial con useState es mejor, básicamente porque se entiende mejor y es más corto

Pero la extracción posterior ya sugiere que escala mejor en escenarios más complejos

Vamos a verlo añadiendo un estado para cada tarea

js
import React, { useRef, useReducer } from 'react'
export const App = () => {
const inputRef = useRef()
const [elements, setElements] = useReducer((state, action) => {
return action.type === 'add'
? [{ value: action.value, status: action.status }, ...state]
: action.type === 'remove'
? [...state.slice(0, action.i), ...state.slice(action.i + 1)]
: action.type === 'set'
? state.map((el, j) => (j === action.i ? { ...el, status: action.status } : el))
: []
}, [])
const addElement = () =>
setElements({
type: 'add',
value: inputRef.current.value,
status: 'pending',
})
const removeElement = i => setElements({ type: 'remove', i: i })
const doneElement = i => setElements({ type: 'set', i: i, status: 'done' })
return (
<div>
<input ref={inputRef} />
<button onClick={addElement}>✏️</button>
{elements.map((el, i) => (
<div key={'element' + i}>
<span>
{el.value} {el.status}
</span>
<button onClick={() => doneElement(i)}>✔️</button>
<button onClick={() => removeElement(i)}></button>
</div>
))}
</div>
)
}
export default App
Ver en CodeSandBox

Al igual que antes, este código nos permite entenderlo todo muy rápidamente excepto la función useReducer

Esto es bueno ya que tendremos una idea de qué hace esa parte del código y podremos seguir adelante

Si lo hacemos con useState estaremos exactamente en la misma situación

js
import React, { useRef, useState } from 'react'
export const App = () => {
const inputRef = useRef()
const [elements, setState] = useState([])
const setElements = action =>
setState(
action.type === 'add'
? [{ value: action.value, status: action.status }, ...elements]
: action.type === 'remove'
? [...elements.slice(0, action.i), ...elements.slice(action.i + 1)]
: action.type === 'set'
? elements.map((el, j) => (j === action.i ? { ...el, status: action.status } : el))
: []
)
const addElement = () =>
setElements({
type: 'add',
value: inputRef.current.value,
status: 'pending',
})
const removeElement = i => setElements({ type: 'remove', i: i })
const doneElement = i => setElements({ type: 'set', i: i, status: 'done' })
return (
<div>
<input ref={inputRef} />
<button onClick={addElement}>✏️</button>
{elements.map((el, i) => (
<div key={'element' + i}>
<span>
{el.value} {el.status}
</span>
<button onClick={() => doneElement(i)}>✔️</button>
<button onClick={() => removeElement(i)}></button>
</div>
))}
</div>
)
}
export default App
Ver en CodeSandBox

Es decir, aquí lo entendemos todo a la perfección excepto la función setElements, que es la parte que antes estaba integrada en el useReducer

Por lo tanto la decisión no es si utilizar useState o useReducer, sino en cómo organizar la modificación de nuestro estado

  • Podemos trabajar con distintos estados

  • Podemos trabajar con un único estado y modificarlo con distintas funciones

  • Podemos trabajar con un único estado y modificarlo con una única función

Depende de nuestras necesidades escogeremos una estrategia u otra, y entonces es cuando el useReducer nos puede dar la oportunidad de ahorrarnos algunas líneas de código que siempre se agradece

El objetivo siempre tiene que ser augmentar la legibilidad del código, o en otras palabras, aplicar todo lo que se puede el principio KISS

🙋‍♂️

Qué tal la entrada?
👌 Bien 🙌🙌
👍 Bien, pero algunas cosas podrían explicarse mejor 😬
🤷‍♂️ Da por sentadas demasiadas cosas 😒
🤷‍♂️ A ver, hay poca chicha 😬
🤷‍♂️ Los ejemplos no son muy claros 🙇‍♂️
🤷‍♂️ No se entiende, está mal escrito 👎
✍️ Hay errores, revísalo en cuanto puedas 🙏

Más entradas

Privacidad
by kuworking.com
[ 2020 >> kuworking ]