Creación de Middleware Personalizado en Redux
Clase 11 de 22 • Curso Profesional de React.js y Redux
Contenido del curso
Clase 11 de 22 • Curso Profesional de React.js y Redux
Contenido del curso
Franco Figueroa Flores
Emilio Nicolás Mendoza Patti
SANDRO SIMON
Pedro Iván Juárez Cornejo
César Palma
Jose Herrera
Sergio Arenas
Zaidibeth Ramos
Rafael Soriano Ramírez
José Galdámez
Jorge Rivadeneira Cevallos
José Galdámez
Andrés Felipe Eslava Zuluaga
Constanza Lopez modinger
Kengya Moncada
Fernanda Aguilar Pfannschmidt
Juan Camilo Lentino Villalba
Alex Guaman
Set Baruch Martínez Jiménez
Agustín Nicolás Stringa
Samuel Sanchez Pardo
Mariangelica Useche
David Rangel
David Rangel
Andrés Serafin Rene Jerez Camargo
Juan Diego Mejia Maestre
Jose Alexis Nuñez Torres
Harold Zurita Simon
Julio Jimmy Cuadros
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); }
No se me ocurria nada para hacer entonces lei tu propuesta y arme los mismo de esta manera
export const prefix = (store) => (next) => (action) => { const newPayload = action.action.payload.map(item => ({...item, name: `Poke-${item.name}`})) const updatedAction = {...action, action: {...action.action, payload: newPayload}} 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); }; }; };
Honestamente yo me quedo con Context API, no dudo que Redux sea buena herramienta, pero sin duda es mas complicada. (Y deja que llegues al redux-toolkit) 😢😢😢
Si, esta es la versión larga. En programación funcional, a esto se le llama hacer currying. https://yeisondaza.com/currying-en-javascript-funciones-con-superpoderes
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) }
Otra manera de hacer lo mismo seria
export const number = (store) => (next) => (action) => { const counted = action.action.payload.map( (pokemon, i) => ({ ...pokemon, name: `${[i+1]}. ${pokemon.name}` }) ) const updatedAction = { ...action, action:{...action.action, payload: counted} } next(updatedAction)
En la industria y proyectos reales, ¿en qué casos de uso podemos implementar middlewares?, digamos en un eccomerce
Imagina que tienes el dispatch "realizar-pago". Puedes llamarlo y un middleware se encarga de validar si los datos de la tarjeta son correctos y realizar el cobro. Si todo sale bien setea en el store el mensaje de "Todo correcto" o si algo falla "Error". Es una función intermedia entre la acción del usuario y el seteo de la información en el estado.
Para que es el uso de middlewares en Redux, cuál es el objetivo en éste ejemplo?
Los middelware se usan más que todo cuando quieres hacer algo antes de pasar a setearlo en el state. Por ejemplo, puedes hacer un middleware que transoforme la información en un formato específico, que agregué tu usuario o que verifique si tienes permisos. Incluso puedes hacer middleware asincronos que consulten base de datos. En este ejemplo está algo confuso, pero ten en mente que es una función intermedia entre la petición y el seteo de la información en el estado.
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
gracias,me salvaste, tenia error con el payload :C
no entiendia porque en mi middlewares actualizaba pero esa actualizacion no llegaba al reducers un poco confuso...
const updateAction = { ...action, action: { ...action.payload, payload: featured }, };
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.
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); }; ```
Si están trabajando con ts, encontré esta solución y, hasta el momento parece funcionar
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) }
¡Excelente, Samuel! 👏🏽
Middleware para pasar el nombre de los pokemons a uppercase:
export const upperCaseMiddleware: Middleware<{}, RootState> = (_store) => (next) => (action: any) => { if (action.type === SET_POKEMONS && action.payload) { const upperCasePayload = action.payload.map((pokemon: any) => ({ ...pokemon, name: pokemon.name.toUpperCase(), })); const updatedAction = { ...action, payload: upperCasePayload, }; return next(updatedAction); } return next(action); };
Así sería con TypeScript
// app/middlewares/index.ts import { type Middleware } from '@reduxjs/toolkit'; import { type RootState } from '~/store/store'; export const logger: Middleware<{}, RootState> = (_store) => (next) => (action) => { console.log(action); return next(action); }; export const featuring: Middleware<{}, RootState> = (_store) => (next) => (action: any) => { if (action.type === 'SET_POKEMONS' && action.payload) { const featured = [{ name: 'Grace' }, ...action.payload]; const updatedActionInfo = { ...action, payload: featured, }; return next(updatedActionInfo); } return next(action); }; // app/store/store.ts import { legacy_createStore as createStore, combineReducers, compose, applyMiddleware, } from '@reduxjs/toolkit'; import { pokemonsreducer } from '~/reducers/pokemonsReducer'; import { featuring, logger } from '~/middlewares'; export const rootReducer = combineReducers({ pokemonData: pokemonsreducer, }); export type RootState = ReturnType<typeof rootReducer>; const composeAlt = (typeof window !== 'undefined' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; const composedEnhancers = composeAlt(applyMiddleware(logger, featuring)); export const store = createStore(rootReducer, composedEnhancers);
Agregué un middleware para agregar un tipo a cada pokemon, ahora todos serán normales xD
export const addNormalType = (store) => (next) => (action) => { if (action.type === 'SET_POKEMONS') { const modifiedPayload = action.payload.map((pokemon: any) => ({ ...pokemon, type: 'normal' })); return next({ ...action, payload: modifiedPayload }); } return next(action); }
🌉 Redux Middlewares: El Interceptor de Acciones
Un Middleware es código que se ejecuta después de disparar una acción, pero antes de que llegue al Reducer. Es el lugar ideal para efectos secundarios (side effects).
🎯 Casos de Uso Principales
fetch o axios (ej. Redux Thunk o Saga).payload de una acción sobre la marcha.🧬 Anatomía de un Middleware (Currificación)
Un middleware usa funciones anidadas para acceder al store, al siguiente paso y a la acción:
const myMiddleware = store => next => action => { // 1. Lógica antes de que el reducer reciba la acción console.log("Acción detectada:", action.type); // 2. Pasar la acción al siguiente middleware o al reducer const result = next(action); // 3. Lógica después de que el estado se actualizó return result; };
Pero no la manera mas nueva es usar los toolkits??
import {createSlice} from "@reduxjs/toolkit"; import {initialState} from "./pokemon.initial.state.ts"; import {pokemonThunks} from "./pokemon.thunk.ts"; const pokemonSlices = createSlice({ name: 'pokemon', initialState, reducers: {}, extraReducers: builder => { builder .addCase(pokemonThunks.all.pending, (state) => { state.status = 'loading' }) .addCase(pokemonThunks.all.fulfilled, (state, action) => { state.status = 'succeeded' state.result = action.payload }) .addCase(pokemonThunks.all.rejected, (state, action) => { state.status = 'failed' state.error = action.error.message || 'error desconocido' }) } }) export default pokemonSlices;
Un middleware es una pieza de código 🧩 que se ejecuta entre el momento en que algo recibe una request (solicitud) y el momento en que da una response (respuesta).
👉 Es una técnica muy utilizada, por ejemplo, en el manejo de servidores backend.
⚙️ Middlewares en Redux
En Redux, el middleware se ejecuta entre que se dispara una acción (action) y esta llega al reducer.
Podemos usar middlewares para muchas tareas útiles, como:
En resumen, un middleware actúa como un “interceptor” que puede modificar, registrar o detener una acción antes de que llegue al reducer.
📘 Glosario esencial
🏪 Store Creator (createStore)
Función que crea el Store de Redux, el corazón donde se guarda el estado global.
🚀 Enhancer (Potenciador)
Un enhancer es una función de orden superior que toma un store creator y devuelve una versión potenciada 💪 con funcionalidades adicionales.
Por ejemplo, los Redux DevTools son un enhancer, ya que extienden la funcionalidad del store.
🧩 Compose
Es una utilidad de programación funcional que combina funciones de derecha a izquierda ➡️⬅️.
En Redux, lo usamos para combinar varios enhancers o middlewares en un solo flujo.
🧑💻 Creando un Custom Middleware
En la carpeta middlewares, creamos un archivo index.js donde definiremos nuestros middlewares personalizados.
Por ejemplo, aquí hay dos:
logger: muestra en consola cada acción que se ejecuta.capitalizing: convierte la primera letra del nombre de cada Pokémon en mayúscula.export const logger = (store) => (next) => (action) => { console.log(action); next(action); }; export const capitalizing = (store) => (next) => (action) => { const capitalized = action.action.payload.map((pokemon) => ({ name: pokemon.name.charAt(0).toUpperCase() + pokemon.name.slice(1), url: pokemon.url })); const capitalizedAction = { ...action.action, payload: capitalized }; next({ ...action, action: capitalizedAction }); };
🏗️ Agregando los Middlewares al Store
Una vez creados, los añadimos al store usando applyMiddleware() y los combinamos con otros enhancers (como los DevTools) mediante compose().
import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import { applyMiddleware, compose, legacy_createStore as createStore } from "redux"; import { pokemonsReducer } from "./reducers/pokemons"; import { Provider } from "react-redux"; import { logger, capitalizing } from "./middlewares"; const root = ReactDOM.createRoot(document.getElementById("root")); const composedEnhancers = compose( window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(capitalizing, logger) ); const store = createStore(pokemonsReducer, composedEnhancers); root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
⚠️ Importante: el orden de los Middlewares
El orden dentro de applyMiddleware() 🧭 sí importa.
Por ejemplo:
logger antes que capitalizing, el logger mostrará la acción antes de que los nombres estén en mayúscula.Por eso, en este caso, el orden correcto es:
applyMiddleware(capitalizing, logger)
Cross-Origin Read Blocking (CORB) ha bloqueado la respuesta de orígenes cruzados <URL> con el tipo de MIME application/json. Consulta la página <URL> para obtener más información. ¿Cómo se arregla ese error sin intervenir el backend?.