¿Que es un middleware en redux?
Published: 2022-04-08 14:59:09

# Middlewares

Los Middlewares son una parte fundamental en el entorno de Redux, quizás en la práctica
solo tengamos que aprender a utilizar ciertos middlewares como [redux logger](https://www.npmjs.com/package/redux-logger) por motivos de `logging` o [redux thunk](https://www.npmjs.com/package/redux-thunk) para manejar peticiones asíncronas.
Pero sería correcto aprender un poco más de ellos para tener un mejor entendimiento de nuestro código, y tener en cuenta la creación de un middleware a la hora de plantear una solución.

# ¿Ques un middleware?


Un middleware no es más que forma de interceptar las acciones que se envían a los reducers, dando la potestad de poder alterar el flujo de ejecución.

Dentro del código, un middleware tiene la siguiente forma:


```ts
export const myMiddlware = (api) => (next) => (action) => {
  /* do something */
};
```


Cuando yo vi esto por primera vez, admito que no entendí mucho, así que decidí investigar un poquito más :).


# ¿De donde vienen los middlewares?


Bien, redux tiene muchos beneficios y uno de ellos es que el estado es inmutable. Cada acción que de como resultado un cambio en el estado, generará un nuevo estado y se guardará.

Entonces, teniendo cuenta esto, sería genial poder registrar todos esos cambios, o solo imprimirlos por consola.

Podemos plantear las siguientes soluciones:

## Wrappear la función dispatch

```tsx
const useDispathcAndLog = () => {
  const dispatch = useDispatch();
  const store = useStore();

  const dispatchAndLog = (action: Action) => {
    console.log("dispatching", action);
    dispatch(action);
    console.log("next state->" + store.getState());
  };
  return dispatchAndLog;
};
```

Esto solucionaría el problema, pero vamos a tener que importar esta función todo
el tiempo. Es una solución poco eficiente y no es la mejor.

## Monkey patching


**Monkey patching** es una técnica utilizada para modificar el comportamiento por defecto
de una función en tiempo de ejecución. Es una buena opción para funcionar como [Polyfil](https://developer.mozilla.org/es/docs/Glossary/Polyfill).

Entonces podemos aplicar esta técnica para llamar al mismo método del store, sin tener
que importarlo

```ts
const monkeyPathchingDispatch = () => {
  const next = store.dispatch;
  store.dispatch = (action) => {
    console.log("dispatching", action);
    let result = next(action);
    console.log("next state", store.getState());
    return result;
  };
};
monkeyPathchingDispatch();
```

Bien, analicemos lo que está pasando aquí:

- Guardo el dispatch original en una variable llamada `next`
- Sobreescribo la función original agregando la funcionalidad de `logging` y ejecuto la original, porque a la vista del usuario
  esto no tiene que tener cambio alguno.
- Se ejecuta la función al inicio de la aplicación


Bien, hasta aquí esto funciona muy bien, pero nos enfrentamos a ciertos problemas con este enfoque.


Bien, hasta aquí esto funciona muy bien, pero nos enfrentamos a ciertos problemas con este enfoque.



- Esta técnica es considerada peligrosa, porque, este no es un comportamiento por defecto
  de la api de Redux.

- Tener uno o más middlewares puede causar redundancia de código.


Para solucionar estos inconvenientes, se puede optar por hacer una manera genérica
de aplicar varios  middleware


```ts
const loggerMiddleware = (store) => {
  const next = store.dispatch;
  return (action) => {
    console.log("dispatching", action);
    let result = next(action);
    console.log("next state", store.getState());
    return result;
  };
};

const createErrorAction = (message: string) => {
  return {
    type: "ERROR",
    message: message,
  };
};

const errorMiddleware = (store) => {
  const next = store.dispatch;
  return (action) => {
    try {
      return next(action);
    } catch (error: { message: string }) {
      return next(createErrorAction(error?.message));
    }
  };
};

const createErrorAction = (message: string) => {
  return {
    type: "ERROR",
    message: message,
  };
};

const errorMiddleware = (store) => {
  const next = store.dispatch;
  return (action) => {
    try {
      return next(action);
    } catch (error: { message: string }) {
      return next(createErrorAction(error?.message));
    }
  };
};

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();
  middlewares.forEach((middleware) => (store.dispatch = middleware(store)));
}
applyMiddlewareByMonkeypatching(store, [logger, errorMiddleware]);
```


Hasta este momento se ha reducido la necesidad de aplicar la técnica por cada middleware. a solo una, pero todavía se sigue implementando.

## Removiendo Monkey Patching

Todo lo anterior funciona, pero no de una manera muy eficiente, el primer problema
es que al hacer una llamada a `store.dispatch` se llama a la función original.
Durante la ejecución del middleware, puede que este no sea el comportamiento que se desee.

En este caso el middleware `errorMiddleware` al no acceder a dispatch wrapeado por
`loggerMiddleware`, no podrá imprimir los detalles de la acción.

De ahí viene la idea de aceptar la función `next` como parámetro, la cual es la
función previamente wrapeada por el middleware anterior.

```ts
const loggerMiddleware = (store) => {
  return (next) => (action) => {
    console.log("dispatching", action);
    let result = next(action);
    console.log("next state", store.getState());
    return result;
  };
};
const errorMiddleware = (store) => {
  return (next) => (action) => {
    try {
      return next(action);
    } catch (error: { message: string }) {
      return next(createErrorAction(error?.message));
    }
  };
};
```


Ahora next no es obtenido del store ,en lugar de eso, se acepta como parámetro

Ahora solo quedaría la aplicación de estos middleware funcionaría así.

```ts
function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();
  let dispatch = store.dispatch;
  middlewares.forEach((middleware) => (dispatch = middleware(store)(dispatch)));
  return Object.assign({}, store, { dispatch });
}
```

Esta no es una implementación real de redux.
[applyMiddleware](https://github.com/reduxjs/redux/blob/master/src/applyMiddleware.ts). La función original hace ciertas mejoras.

- El store solo expone `dispatch` y `getState`
- Se asegura de que llama a `Store.dispatch`, su acción recorrerá toda la cadena de middlewares.
- Se asegura de que solo llame a `applyMiddleware` una vez.

El procedimiento de este artículo fue de la documentación de [Redux](https://redux.js.org/understanding/history-and-design/middleware#attempt-3-monkeypatching-dispatch).




# Conclusión


Los middleware, sirven poder hacer algo con la acción antes de que llegué
al reducto final. La primera función del middleware es una función reducida del `store`, la segunda función es la función `dispatch` previamente empaquetada y la última es la función
que será llamada por el usuario.


**Happy Coding**😁