Patrón de diseño React Context Reducer

October 24, 2022

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í.


Profile picture

Escrito por Jose Le gusta construir cosas y esta empezando a escribir. Twitter

© 2022, manyoloswag