You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

1 Días
2 Hrs
14 Min
35 Seg

Middlewares

11/22
Resources

What are middleware in the context of Redux?

Middleware are pieces of code that are executed between the triggering of an action and its arrival at the reducer in Redux. This technique, widely used in backend server management, allows you to perform several essential tasks for application development:

  • Error Handling: You can integrate error logs and send them to services such as Sentry.
  • Data Fetch: Make requests and manage data efficiently.
  • Application Debugging: Helps debug the flow of data in your application.

How does middleware work in Redux?

To understand middleware, it is crucial to remember some key Redux concepts:

  1. Store Creator: Function that creates the Redux Store.
  2. Enhancers: Higher-order functions that take a Store Creator and return an enhanced version.
  3. Compose: Functional programming utility that combines multiple functions in an orderly fashion.

Creating Custom Middleware

The implementation of a custom middleware, called Logger, is based on curried functions, where each function returns another function:

const logger = store => next => action => { console.log('Dispatching action:', action); return next(action);};
  • The first function receives the store.
  • next is called when the middleware finishes its work and passes the action to the reducer.
  • action contains the information of the triggered action.

Integration of the Middleware in our Application

To add a middleware:

  1. Import the middleware and Redux:

    import { createStore, applyMiddleware, compose } from 'redux';import logger from './middleware/logger';
  2. Use applyMiddleware to integrate it:

    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;const store = createStore( rootReducer, composeEnhancers(applyMiddleware(logger)));

This approach allows you to add various middleware and enhancers, empowering the Store.

Advanced implementation: Manipulating actions with middleware

Modifying actions before they reach the reducer can enrich your application. Imagine we receive data from an API and want to add a custom Pokémon:

Creating Featuring Middleware

const featuring = store => next => action => { if (action.type === 'FETCH_POKEMON_SUCCESS') { const newPayload = [{ name: 'Eddy' }, ...action.payload]; const updatedAction = {... action, payload: newPayload }; return next(updatedAction); } return next(action);};

Multiple Middleware Application

The applyMiddleware function accepts multiple middleware:

const store = createStore( rootReducer, composeEnhancers(applyMiddleware(logger, featuring)));

With this practice, we look at how to add and modify data before reaching the reducer, allowing us to customize the user experience.

Challenge for you

I hope these explanations have piqued your interest in middleware. Now I invite you to create your own middleware and share your solution. I look forward to seeing your creativity in action - keep exploring and learning! I'll be waiting for you in the next class to discover how to use third-party libraries as middleware in your project.

Contributions 37

Questions 4

Sort by:

Want to see more contributions, questions and answers from the community?

Este es el middleware que se me ocurrió: Agregar el prefijo “Poke :” al nombre de cada pokemon.

export const prefix = store => next => action => {
    const prefixed = action.action.payload.map( pokemon => ({
        ...pokemon,
        name: 'Poke: ' + pokemon.name
    }) )

    const updatedAction = {
        ...action,
        action: {...action.action, payload: prefixed}
    }
    next(updatedAction);
}

Creo que ha esto se refería la profesora cuando menciona que la curva de aprendizaje es más pronunciada en Redux comparada a Context API. La sintaxis del middleware me parece bien complicada: una función que recibe como argumento otra función la cual a su vez devuelve otra función… y creo que allí termina. Seguro que la puedo memorizar pero me interesa más entenderla. ¿Sería equivalente a escribir lo siguiente?:


export const logger = function(store) {
	return function(next) {
		return function(action) {
  			console.log(action);
  			next(action);
		};
	};
};

Middlewares:
Es una pieza de código que se ejecuta cuando X recibe un request y ese mismo X da respuesta al request.

Ayuda a los desarrolladores a diseñar aplicaciones con mayor eficiencia. Además, actúa como hilo conductor entre las aplicaciones, los datos y los usuarios.

Los podemos usar para:

  • Hacer logs de errores

  • Hacer fetch de data

  • Depurar nuestra aplicación

  • También podemos customizar nuestra data con applyMiddleware

Mi middleware personalizado fue agregarle un numero indexado a cada pokemon. Aqui el codigo

export const number = (store) => (next) => (action) => {
    let arrayPokeWithNumber = [...action.action.payload] 
    for (let i = 0 ; i < arrayPokeWithNumber.length; i++) {
        arrayPokeWithNumber[i].name = `${[i]} - ${arrayPokeWithNumber[i].name}` 
    }
    const newFeature = {
        ...action,
        action:{...action.action, payload: arrayPokeWithNumber}
    }
    next(newFeature)
}

Para quién venga con TS, le dejo el código que a mí me ha funcionado. Reconozco que ha faltado tipar algunas cosas, pero es porque lo estoy practicando.
.
En este caso, me cost;o entender que lo que aparece en el video, no es lo mismo a la respuesta que me estaba dando en consola cuando se mostraba información en el logger. Acá dejo el resultado:

// ** index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { pokemonsReducer } from './reducers/pokemons';
import { Provider } from 'react-redux';
import { applyMiddleware, compose, legacy_createStore as createStore } from 'redux';
import { featuring, logger } from './middlewares';
import './index.css';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

const composedEnhancers = compose(
  applyMiddleware(logger, featuring),
  (window as any).__REDUX_DEVTOOLS_EXTENSION__ && (window as any).__REDUX_DEVTOOLS_EXTENSION__(),
)

const store = createStore(pokemonsReducer, composedEnhancers);

root.render(
  <Provider store={store}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </Provider>
);

.

// ** Middlewares/index.ts

export const logger = (store: any) => {   // store de la aplicación
  return (next: any) => {          // next es una función que se llama cuando el middleware termina su trabajo y envía el action al reducer
    return (action: any) => {      // action es la información que se pasa al reducer
      console.log(action);
      next(action); // <-- hace que el action llegue al reducer
    } 
  }
};

export const featuring = (store: any) => {
  return (next: any) => {
    return (actionNew: any) => {
      debugger;
      const featured = [
        { name: 'Momea', url: 'https://ab.c' }, // <-- nuevo pokemon que quiero meter al state
        ...actionNew.payload, // <-- desestructura todos los pokemons que están en el payload de 'action'
      ];
      const updatedAction = {
        ...actionNew,
        payload: [
          ...featured,
        ]
      }
      next(updatedAction);
    } 
  }
};

No tengo el mismo objeto, por eso en el middleware cambié la forma de desestructurar todo el array

Middleware para invertir el nombre de los pokemones 😃

export const reversed = (store) => (next) => (action) => {
    const reversedPkm = action.action.payload.map((item) => {
        const split = item.name.split('');
        const reverseArray = split.reverse();
        const joinArray = reverseArray.join('');

        return { ...item, name: joinArray };
    });

    const updated = { ...action, action: { ...action.action, payload: reversedPkm } };

    next(updated);
};

la funcionalidad de mi middleware personalizado fue ordenar el arreglo de pokemones en orden alfabético a partir de la propiedad name de cada uno

export const myMiddleware = (store) => (next) => (actionInfo) => {
    const alphabetPokemon = actionInfo.action.payload.map(poke => poke.name).sort()
    const pokemonPayload = actionInfo.action.payload.map((poke, index) => ({ ...poke, name: alphabetPokemon[index] }))

    const myFormatPokemon = {
        ...actionInfo,
        action: { ...actionInfo.action, payload: pokemonPayload}       
    }

    // console.log(myFormatPokemon)
    next(myFormatPokemon)
}

Agregue una nueva propiedad dentro del arreglo de pokemons con el numero del pokemon con el formato #001

export const enumerated = (store) => (next) => (action) => {
    const list = [ ...action.action.payload];    
    const listUpdated = [];
    let count = 0;    
    list.forEach( (pokemon) => listUpdated.push({...pokemon, number: "#" + (++count).toString().padStart(3,'0') }));
    const updatedAction = {...action, action: {...action.action, payload: listUpdated}}
    next(updatedAction);
};
Creo que me adelanté, pero hice un middleware para generar la url del logo de cada Pokémon. ```js export const getUrlLogo = (store) => (next) => (action) => { const getId = (url) => { if(typeof url === 'undefined') return 0; // Para el que nosotros agregamos o si viene vacío. const parts = url.split('/'); const lastPart = parts.pop() || parts.pop(); return lastPart; }; const pokeList = action.action.payload.map( pokemon => ({ ...pokemon, url_image: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/"+getId(pokemon.url)+".png", })); const updatedAction = { ...action, action: { ...action.action, payload: pokeList} }; return next(updatedAction); }; ```![](https://static.platzi.com/media/user_upload/image-d038442d-c6ec-4915-a6d0-90089485f6f1.jpg)

Si están trabajando con ts, encontré esta solución y, hasta el momento parece funcionar
https://www.npmjs.com/package/redux-devtools-extension

Hice un middleware que pone la primera letra del nombre del Pokémon en mayúscula.

export const nameUpperCase = (store) => (next) => (actionInfo) => {

    const featured = [
         ...actionInfo.action.payload.map(
            pokemon => ({
                ...pokemon,
                 name: pokemon.name.charAt(0).toUpperCase() + 
                        pokemon.name.slice(1)
            })
        )
    ]

    const updateActionInfo = {
        ...actionInfo,
        action: {
            ...actionInfo.action,
            payload: featured
        }
    }
    next(updateActionInfo)
}
Para los que están usando vite, modifiquen index a los siguinete: import { StrictMode } from 'react';import { createRoot } from 'react-dom/client';import { pokemonReducer } from './reducers/pokemon';import { Provider } from 'react-redux';import { applyMiddleware, compose, legacy\_createStore as createStore } from 'redux';import App from './App';import { logger } from './middlewares/logger'; const composeEnhancers =  window.\_\_REDUX\_DEVTOOLS\_EXTENSION\_\_ && window.\_\_REDUX\_DEVTOOLS\_EXTENSION\_\_() || compose; const store = createStore(  pokemonReducer,  composeEnhancers(    applyMiddleware(logger)  )); createRoot(document.getElementById('root')).render(  \<Provider store={store}>    \<StrictMode>      \<App />    \</StrictMode>  \</Provider>)
El `compose` en Redux es una función de programación funcional que permite combinar múltiples funciones de manera que se puedan aplicar secuencialmente. En el contexto de Redux, se utiliza para combinar enhancers del store, lo que permite agregar múltiples funcionalidades a la vez, como middleware y herramientas de desarrollo (por ejemplo, Redux DevTools). Esto se hace permitiendo que cada enhancer modifique el store regresado por el anterior, facilitando la gestión y escalabilidad de la aplicación.
![](https://static.platzi.com/media/user_upload/image-2fe84156-1c19-40dc-bbe5-7b3a4565093d.jpg) web ![](https://static.platzi.com/media/user_upload/image-9e73a91c-fed3-4ce3-8ef6-7bd4532d2f70.jpg)
Mi middleware es solo agregarle el número en el nombre `export const numbersItems = (store) => (next) => (actionInfo) => {  const myarr = actionInfo.action.payload;  var num = 1;` `  myarr.forEach(element => {    element.name = num +' '+ element.name;     num++;  });` `  const updatedActionInfo = {    ...actionInfo,    action: { ...actionInfo.action, payload: myarr },  };  next(updatedActionInfo);};`
Mi middleware regresa si pikachu existe o no en la respuesta de la api `export const isPikachu = (store) => (next) => (actionInfo) =>{    const fetchedInfo = actionInfo.action.payload;    let pikachu = false;    fetchedInfo.map((item)=>{        let name = item.name        if(name=='pikachu') {            pikachu=true        return 0}    })    console.log('pikachu state: ' + pikachu?'exists':'not exists')    next(actionInfo)}`
Agregue el numero de pokemon del arreglo y lo concatene alnombre ![](https://static.platzi.com/media/user_upload/image-acb64d4f-3a44-4bfe-afc6-d0bc1ed121a9.jpg)
### Glosario #### Store Creator Es una función que crea el store de redux. #### Enhancer Potenciadores del store. Son funciones de orden superior que toma un store creator y devuelve una versión potenciada. Similar a los middlewares ya que te permite alterar la interfaz del store de manera combinable, por ejemplo los dev tools es un potenciador del store. #### Compose Es una herramienta de programación funcional que combina funciones de derecha a izquierda y lo usamos para poder tener mutiples potenciadores del store.

Mi middleWare que pone la primera letra del pokemon en mayuscula

export const capitalizePokemons = (store)=>(next)=>(action)=>{
    const capitalize = (string) => {
        return string.charAt(0).toUpperCase() + string.slice(1);
    }
    
    const capitalizedPokemons = action.action.payload.map((pokemon) => {
        return {
            ...pokemon,
            name: capitalize(pokemon.name),
        };
    });
    const updatedActionInfo = {
        ...action,
        action: {
            ...action.action,
            payload: capitalizedPokemons
        }
    }
    return next(updatedActionInfo)
}
Hola este es mi middleware transformar los nombres a mayúsculas aunque que ahora que lo pienso es mejor hacerlo con CSS xd. ```js export const capitalize = () => (next) => (actionInfo) => { const capitalize = actionInfo.action.payload.map((pokemon) => ({ ...pokemon, name: pokemon.name.toUpperCase(), })); const updateActionInfo = modifyPayload(actionInfo, capitalize); next(updateActionInfo); }; ```

Aca mi middleware que ordena por nombre descendente usando typescript y chatgpt:

type Order = 'ASC' | 'DES';

export function orderByParam<T>(list: T[], param: keyof T, order: Order = 'ASC'): T[] {
  const multiplier = order === 'ASC' ? 1 : -1;

  return list.sort((a: T, b: T) => {
    const valueA = a[param];
    const valueB = b[param];

    if (valueA < valueB) {
      return -1 * multiplier;
    } else if (valueA > valueB) {
      return 1 * multiplier;
    } else {
      return 0;
    }
  });
}
import { Middleware } from 'redux';
import { orderByParam } from '../utils/strings';
import { Pokemon } from '../actions/types';

export const orderByNameDesc: Middleware = (store) => (next) => (action) => {
  const {action: oldAction} = action;
  const orderList = orderByParam<Pokemon>(oldAction.payload, 'name', 'DES')
  const updatedActionInfo = {
    ...action,
    action: { ...oldAction, payload: orderList },
  };
  next(updatedActionInfo);
};

Proyectos:
Medianos: si quieres usa redux
Pequeño a mediano: Zustand
Mediano a grande: Relay, ReactQuery, React Apollo, SWR

Que sintaxis tan backend-like

MI middleware que agrega un valor aleatorio de poder para cada pokemon

export const addPower = (store: any) => (next: any) => (actionInfo: any) => {

  const featured = actionInfo.payload.map((e: any) => ({ ...e, "power": Math.floor(Math.random() * 100) + 1 }))
  const updatedAction = {
    ...actionInfo, payload: featured
  }
  next(updatedAction)
}

mi middleware

Reto del Middleware:

Poner los nombres de los pokemos en mayúscula:

¡Y este fue el resultado! 🎉

El compañero @CésarPalma dejó el siguiente comentario que me pareció perfecto para entender un poco mejor…

En programación funcional, a esto se le llama hacer currying.
https://yeisondaza.com/currying-en-javascript-funciones-con-superpoderes

Agregar la imagen del pokemon:

export const addImageToPokemons = (store) => (next) => async (action) => {
    let pokemons = action.action.payload

    for (let i = 0; i < pokemons.length; i++) {
        const element = pokemons[i];
        let data = await getImagePokemon(element.name)
        let image = data.data.sprites.front_default

        element.image = image
    }

    next(action)
}

¿Esto esta bien implementado? Porque no necesito pasar la copia del estado 😕

Me esta gustando mucho este curso. La profesora se deja entender muy bien.

Hice un middleware que elimina un pokemon random de todos los que están disponibles, deje console.log para que puedan ver cuál fue el seleccionado y como quedan los pokemons sin ese, aqui esta el middleware ==>

export const removeAnyPokemon = (_store) => (next) => (action) => {
    if (action.action.payload.length === 0) {
        return next(action);
    };

    const numberIdToDelete = Math.floor(Math.random() * action.action.payload.length);
    const pokemonToEliminate = action.action.payload[numberIdToDelete];

    console.log('Selected Pokemon Number and this Pokemon', numberIdToDelete, pokemonToEliminate);
    console.log('Array and its length before removing', action.action.payload, action.action.payload.length);
    
    action.action.payload.splice(numberIdToDelete, 1);
    
    console.log('Array after deletion and its length',action.action.payload, action.action.payload.length);
    
    next(action);
};

Con Redux Toolkit es 1000 veces más sencillo implementar todos estas funciones. Entiendo que es bueno conocer la forma vieja de hacerlo, pero la explicación está un poco mareada, es muy fácil perderse.

Este fue mi middleware

import { Middleware, Dispatch, Action } from 'redux';

const logger: Middleware = ({ getState }: any) => (next: Dispatch) => (action: Action) => {
  console.log('will dispatch', action);
  const returnValue = next(action);
  console.log('state after dispatch', getState());
  return returnValue;
};

const modifyName: Middleware = ({ getState }: any) => (next: Dispatch) => (action: {type:string,payload:any}) => {
    const payload = action.payload;
    payload.forEach((element: {name:string,url:string}) => {
        element.name = `Name: ${element.name}`
    });
    const returnValue = next(action);
    return returnValue;
};

const middleware = {logger,modifyName};
export default middleware;

Yo utilicé un Middleware para poner la primera letra de cada nombre en mayúscula.

export const upperCaseFirstLetterName = (store) =>(next) => (actionInfo) => {
    
    const pokemonsUp = actionInfo.payload.map(pokemon=>{
        
        const upperPok = pokemon.name.charAt(0).toUpperCase() + pokemon.name.slice(1)
        return {name: upperPok}
    })

    
    const updatedPayload = {
        ...actionInfo,
        payload: pokemonsUp
    }
    next(updatedPayload)
}

Currying es una técnica de programación funcional que permite transformar una función que toma múltiples argumentos en una secuencia de funciones que toman un solo argumento cada una.

El objetivo de currying es hacer que las funciones sean más reutilizables y componibles, permitiendo a los desarrolladores crear funciones más específicas a partir de funciones más generales.

Mi middleware personalizado para agregarle una propiedad adicional “isActive” a cada pokemon:

export const addActivationProp = (store) => (next) => actionInfo => {
    const newPayload = actionInfo.action.payload.map(pokemon => pokemon.isActive = true);
    const newActionInfo = { ...actionInfo, payload: newPayload};
    next(newActionInfo);
}

El middeware que cree fue pokemonsWithId que agrega el id a los pokemons.

import types from '../actions/types';

const pokemonsWithId = (store) => (next) => (actionInfo) => {
  if (actionInfo.action.type === types.setPokemons) {
    const pokemons = actionInfo.action.payload;
    const pokemonsWithId = pokemons.map((pokemon, i) => ({ ...pokemon, id: i + 1 }));
    
    const updatedAction = {
      ...actionInfo,
      action: { ...actionInfo.action, payload: pokemonsWithId },
    };
    next(updatedAction);
  } else {
    next(actionInfo);
  }
};

export default pokemonsWithId;

Para usar los devtools y el middleware lo hice de la siguiente manera, use un paquete

npm install --save-dev redux-devtools-extension

import { composeWithDevTools } from "redux-devtools-extension";

const middlewareEnhancer = applyMiddleware(logger);

const store = createStore(
  pokemonsReducer,
  composeWithDevTools(middlewareEnhancer)
);

funciona perfecto por si alguno tiene un error y está usando TypeScript