Este es un patrón de diseño que me cambio la vida.
Cuando vas comenzando una aplicación usualmente puedes valerte de unos cuantos useState, tus acciones son sencillas y modifican un solo estado dentro del componente.
const MiComponente = () => {
const [contador, setContador] = useState(0)
function sumaUno() {
setContador(contador + 1)
}
return ...
}
mediante pasa el tiempo y tu aplicación crese empiezas separar tu lógica en varios componentes y a mandar cada vez mas estados por props a componentes hijos, quizás una o dos acciones también.
...
const [contador, setContador] = useState(0)
const [descuento, setDescuento] = useState(false)
function sumaUno() {
setContador(contador + 1)
}
function restaUno() {
setContador(contador - 1)
}
return (
<div>
<Opciones
contador={contador}
setDescuento={setDescuento}
/>
<Contador
contador={contador}
sumaUno={sumaUno}
restaUno={restaUno}
descuento={descuento}
/>
</div>
)
...
no tardas en darte cuenta de que tienes 3 componentes los cuales reciben los mismos props (y dentro de estos mandas el mismo prop una vez mas a otro hijo) y es hora de implementar un contexto. comienzas a mover todos tus useState a un contexto y terminas llevando también tus acciones, a este punto tu contexto probablemente luce algo así
const ContextProvider = ({children}) => {
const [contador, setContador] = useState(0)
const [descuento, setDescuento] = useState(false)
function sumaUno() {
setContador(contador + 1)
}
function restaUno() {
setContador(contador - 1)
}
function aplicarDescuento() {
setDescuento(true)
}
return (
<Context.Provider value={{
contador,
descuento,
sumaUno,
restaUno,
aplicarDescuento,
}}>
{children}
<Context.Provider>
)
}
Solucionamos el problema de los props y ahora acceder a tu estado dentro de tus child componentes es tan sencillo como
const useContext = () => {
const context = useContext(GlobalContext)
if (!context) {
// throw error
console.error('You can only access this state from insisde the Global Context')
} else {
return context
}
}
const Condador = () => {
const { contador, sumaUno, restaUno} = useContext()
return ...
}
Aquí lo molesto comienza a ser estar mandado todos tus estados y acciones en el valor del contexto, entre mas crece la aplicación esto se va volviendo mas largo e imposible de mantener.
Bueno ahora toca acomodar este estado y acciones utilizando un reducer, para esto vamos a hacer 3 pasos
- Crear un mapa de nuestras acciones
- Juntar nuestros estados en uno solo
- Crear un reducer donde introduciremos la lógica que tenemo en nuestra acciones
Bueno una vez echo esto luce así
// Declaramos nuestras acciones en un mapa
const ACTIONS = {
sumaUno: 'sumaUno',
restaUno: 'restaUno',
aplicarDescuento: 'aplicarDescuento'
}
// Nuestro estado inicial
const INITIAL_STATE = {
contador: 0,
descuento: false,
}
// Nuestra logica de aplicacion
function reducer(state, action) {
switch (action) {
case ACTIONS.sumaUno:
return { ...state, contador: state.contador + 1 }
case ACTIONS.restaUno:
return { ...state, contador: state.contador - 1 }
case Actions.aplicarDescuento:
return { ...state, descuento: true }
default:
return state
}
}
// Context provider
const ContextProvider = ({ children }) => {
// Inicializa el state del contexto
const [state, dispatch] = useReducer(reducer, INITIAL_STATE)
return (
<GlobalContext.Provider value={{...state, dispatch}}>
{children}
</GlobalContext.Provider>
)
}
Bueno ahora tenemos nuestro estado y acciones organizadas, incluso podemos mover el reducer a otro archivo si este se vuelve muy grande. Otro efecto que generan estos cambios es que ahora acceder a nuestras acciones dentro de los consumidores se ve así
import { ACTIONS, useContext } from './ContextReducer'
const Contador = () => {
const { contador, dispatch } = useContext()
return (
<div>
<p>{contador}</p>
<button onClick={
() => dispatch(ACTIONS.sumaUno)
}>+1</button>
<button onClick={
() => dispatch(ACTIONS.restaUno)
}>-1</button>
</div>
)
}
Nota como se evita el uso de props.
El cambio mas notable aquí es el uso de dispatch y acciones, Gracias a que creamos un mapa hacemos uso de auto completado lo que nos facilita la vida y mantenemos los imports y las declaraciones a un mínimo
Bueno ahora si quieres ver un ejemplo completo de este patrón, dejo un repositorio donde lo puedes ver funcionando Aquí.