Gaston Blanco
Diego Vergara
Eloy Bernardez
Juan Camilo Lentino Villalba
Maicol Hernandez
Carlos Alberto Usuga Martinez
Nicolas Esteban Rojas Cuervo
Andrés Felipe Eslava Zuluaga
Bryan David Castañeda Aranzales
Cayo Legal
Erlyn David Ascarate Lachi
César Palma
Harold Zurita Simon
Gualberto Montiel
Carlos Rodríguez
Luis Enrique Marroquín Orellana
Fabián Nava Pereda
David Rangel
Jose Alexis Nuñez Torres
Company_adminJose Alexis Nuñez Torres
Company_adminCayo Legal
marcos martilotta
marcos martilotta
Nicolas Agustin Goitia
Cayo Legal
Emilio Sala
Sebastian Carballo
Nazareno Rosales
Armando Salamanca Ayon
Para los despistados como yo. En el Index.js agreguen COMPOSE__, no es el mismo que teniamos antes.
Tiene que quedar así:
const composeAlt = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
Gracias, había escrito un dash de más.
¡Muchas gracias compañero! Cometí el mismo error O.o
Redux Thunk
- Es un Middleware que extiende las capacidades del store (enhancer), nos permite realizar acciones asíncronas en nuestros actions creators que por defecto no podemos hacer
- Thunk es un concepto de programación donde se utiliza una función intermedia para retrasar la evaluación o ejecución de una operación, como puede ser la petición de una data a una API y demás acciones
para este caso en particular separaremos responsabilidades, delegando la petición a la API a nuestro action creator y no a el componente como se venia trabajando
Instalación
npm install redux-thunk
Integración simplemente debemos importarlo y añadirlo a nuestra composición de middlewares src/index.js:
import thunk from 'redux-thunk'; ..... .... ... . const composeEnhancers = compose( applyMiddleware(thunk, logger) // ** ) const store = createStore( pokemonsReducer, composeEnhancers ) const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> )
si estamos haciendo uso de la herramienta Redux DevTools debemos hacer pasos adicionales src/index.js:
import thunk from 'redux-thunk'; ..... ... . const composeAlt = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE_ || compose const composeEnhancers = composeAlt( applyMiddleware(thunk, logger) ) const store = createStore( pokemonsReducer, composeEnhancers ) const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> )
Creación action creator una vez integrado procederemos a crear nuestro action creator que será una función la cual contendrá la petición a la API que veníamos trabajando, esa función hará la petición a la API, obtendrá su respuesta y devolverá otra función la cual recibe como parámetro el dispatch para poder disparar acciones, este dispatch recibirá como parámetro la acción setPokemons que a su vez recibirá como parámetro la respuesta de la petición a la API src/actions/index.js:
export const getPokemonsWithDetails = (pokemons = [])=> async (dispatch)=> { const pokemonsDetailed = await Promise.all(pokemons.map(pokemon => getPokemonDetail(pokemon))) dispatch(setPokemons(pokemonsDetailed)) }
Separación de responsabilidades una vez creada el action creator lo importamos y podemos hacer dispatch con el pasándole como parámetro nuestro arreglo de objetos de pokemones src/App.js:
const App = () => { const pokemons = useSelector(state => state.pokemons) const dispatch = useDispatch() useEffect(()=> { const fetchPokemons = async ()=> { const pokemonsRes = await getPokemon() dispatch(getPokemonsWithDetails(pokemonsRes)) // ** } fetchPokemons() }, []) return ( <div className="App"> .... </div> ); }
¡Gracias por el aporte!
Por si alguno le ha ocurriod este error o uno similar por usar custom middlewares Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'payload') Debugeando un poco me di cuenta que las custom middlewares que el payload vienen con ua estructura diferente cuando se ingresa el compose de esta manera con la libreria thunk, ya que no trae la propiedad action dentro del objeto como lo hacia en clases anteriores, por el contrario trae las propiedades type y payload a primer nivel, por lo que corrigiendo esa logica dentro del middelware se corrigio mi problema.
Este Middelware antes lo tenia asi con la propiedad actionInfo.action.payload
export const addNumberToName = (store) => (next) => (actionInfo) => { const featured = [ ...actionInfo.action.payload.map((pokemon, index) => ({ ...pokemon, name: `${index + 1} - ${pokemon.name}`, })), ] const updateActionInfo = { ...actionInfo, action: { ...actionInfo.action, payload: featured }, } next(updateActionInfo) }
Y modificandolo quedo así actionInfo.payload
export const addNumberToName = (store) => (next) => (actionInfo) => { const featured = [ ...actionInfo.payload.map((pokemon, index) => ({ ...pokemon, name: `${index + 1} - ${pokemon.name}`, })), ] const updateActionInfo = { ...actionInfo, payload: featured, } next(updateActionInfo) }
Si no te hubieras tomado la molestia de hacer este aporte estaria por dos horas intentando solucionarlo haha, Muchas gracias!!!!!
Solución de Thunk con TypeScript en la v8.
Quiero aclarar que redux pide que se use RTK para que no existan inconvenientes con el tipado del Disptach Es esta la razón de porque el tipado en mi action-thunk quedó de esta forma que comparto.
.
// src/index.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { applyMiddleware, compose, legacy_createStore as createStore } from 'redux'; import thunk from 'redux-thunk'; import { Provider } from 'react-redux'; import { pokemonsReducer } from './reducers/pokemons'; import { logger } from './middlewares'; import App from './App'; import './index.css'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); const composedEnhancers = compose( applyMiddleware(thunk, logger), (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> );
.
// src/api/index.ts import axios from "axios"; import { IPokemonDetails, IPokemonType } from "../types"; import { PokemonResponseType } from "./responses"; export const getPokemon = async () => { const URL = "https://pokeapi.co/api/v2/pokemon?limit=151"; try { const { data } = await axios.get<PokemonResponseType>(URL); const { results } = data; return results; } catch (err) { console.error("err ".repeat(5), err); } }; export const getPokemonDetails = async (pokemon: IPokemonType) => { try { const { data } = await axios.get<IPokemonDetails>(pokemon.url); console.log(data) return data; } catch (err) { console.error("err ".repeat(5), err); } };
.
// src/actions/index.ts import { IPokemonDetails, IPokemonType } from "../types"; import { Dispatch } from "redux"; import { SET_POKEMONS_DETAILS } from "./types"; import { getPokemonDetails } from "../api"; export const setPokemonsDetails = ( payload: (IPokemonDetails | undefined)[] ) => ({ type: SET_POKEMONS_DETAILS, payload, }); export const getPokemonsDetailsAction = (pokemons: IPokemonType[]) => // Dispatch se debe tipar para que no ocurran inconvenientes con lo que devuelve async (dispatch: Dispatch<any>) => { const pokemonDetails = await Promise.all( pokemons.map((pokemon) => pokemon && getPokemonDetails(pokemon)) ); dispatch(setPokemonsDetails(pokemonDetails)); };
.
// src/App.tsx import React, { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { Col } from "antd"; import Searcher from "./components/Searcher"; import PokemonList from "./components/PokemonList"; import { getPokemon } from "./api"; import { getPokemonsDetailsAction } from "./actions"; import { IPokemonDetails } from "./types"; import logo from "./statics/logo.svg"; import "./App.css"; import { Dispatch } from "redux"; type AppType = { pokemons: IPokemonDetails[]; }; function App() { const pokemons = useSelector((state: AppType) => state.pokemons); // Dispatch se debe tipar para que no ocurran inconvenientes con lo que devuelve const dispatch = useDispatch<Dispatch<any>>(); useEffect(() => { const fetchPokemons = async () => { const response = await getPokemon(); response && dispatch(getPokemonsDetailsAction(response)); }; fetchPokemons(); }, [dispatch]); return ( <div className="App"> <Col span={4} offset={10}> <img src={logo} alt="Pokedux" /> </Col> <Col span={8} offset={8}> <Searcher /> </Col> <PokemonList pokemons={pokemons} /> </div> ); } export default App;
Es un middleware pero, ¿realmente es importante usarlo?, esto se puede hacer con la misma solucion que le habiamos dado al principio o con incluso otra.
Es para separar responsabilidades y que el componente quede más limpio, creo
La elección de usar o no Redux Thunk depende de la complejidad de tu aplicación y de tus preferencias personales. Si te sientes cómodo manejando operaciones asíncronas de otras maneras o si tu aplicación no las necesita, puedes prescindir de Redux Thunk. Sin embargo, para aplicaciones más complejas con operaciones asíncronas, Redux Thunk puede simplificar significativamente la lógica de manejo del estado.
https://chat.openai.com/share/32d31405-6ff8-47fb-a0af-6d7ee413f38e
React custom hooks es mejor para data fetching que redux thunk. https://redux.js.org/style-guide/#use-thunks-for-async-logic
Cuando hacemos fetch de los detalles de nuestros Pokémons, no es recomendable dejar esa responsabilidad a los componentes.
Podemos delegarla a un Action Creator especial, que no solo devuelva un objeto plano, sino que retorne una función que haga la petición a la API.
⚡ Problema con Redux por defecto
🥷 Introducción a Redux Thunk
💻 Instalación de Redux Thunk
npm i redux-thunk
🔧 Action Creator asíncrono para Pokémons
import { SET_POKEMONS } from "./types"; import { getPokemonDetails } from "../api"; // Acción síncrona para actualizar el store export const setPokemons = (payload) => ({ type: SET_POKEMONS, payload, }); // Acción asíncrona usando Thunk export const getPokemonsWithDetails = (pokemons = []) => async (dispatch) => { const pokemonsDetailed = await Promise.all( pokemons.map((pokemon) => getPokemonDetails(pokemon)) ); dispatch(setPokemons(pokemonsDetailed)); };
🔄 Uso en App.js con useEffect
function App() { const { pokemons, setPokemons } = usePokemons(); useEffect(() => { const fetchPokemons = async () => { const pokemonsRes = await getPokemon(); setPokemons(pokemonsRes); // Ahora maneja la lógica asíncrona dentro del action creator }; fetchPokemons(); }, []); // eslint-disable-line react-hooks/exhaustive-deps }
🪝 Custom Hook para Redux
import { useDispatch, useSelector } from 'react-redux'; import { getPokemonsWithDetails } from '../actions'; export const usePokemons = () => { const pokemons = useSelector((state) => state.pokemons); const dispatch = useDispatch(); const handleSetPokemons = (pokemonsData) => { dispatch(getPokemonsWithDetails(pokemonsData)); }; return { pokemons, setPokemons: handleSetPokemons, }; };
🛠 Configuración de middleware en index.js
import thunk from 'redux-thunk'; import logger from 'redux-logger'; import { createStore, applyMiddleware, compose } from 'redux'; import rootReducer from './reducers'; const composeAlt = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const composedEnhancers = composeAlt(applyMiddleware(thunk, logger)); const store = createStore(rootReducer, composedEnhancers);
✅ Con esto:
Redux Thunk es un middleware de Redux que permite a los desarrolladores escribir acciones que retornan funciones en lugar de objetos. Estas funciones pueden realizar operaciones asíncronas, como llamadas a una API, y después dispatch acciones cuando se han completado dichas operaciones. De esta manera, Redux Thunk permite gestionar la lógica de la aplicación asíncrona dentro de las acciones de Redux, lo que puede mejorar la organización y claridad del código.
Redux es como lo era Vuex para Vue.js, un despelote total, luego evolucionó a Pinia y bum, un cambio de enfoque muy simple. Redux se está tardando en hacer esto y se puede quedar atrás respecto a otras alternativas
Estoy trabajando con la versión 3.1.0 de redux-thunk y aparentemente no tiene un export default llamado thunk, tuve que importarlo de esta forma:
import { thunk } from 'redux-thunk';
Que es un thunk?
Les comparto cómo se tiene que modificar para TypeScript:
// app/store/store.ts import { legacy_createStore as createStore, combineReducers, compose, applyMiddleware, type Action, } from '@reduxjs/toolkit'; import { thunk, type ThunkDispatch } from 'redux-thunk'; import { pokemonsreducer } from '~/reducers/pokemonsReducer'; import { 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, thunk)); export const store = createStore(rootReducer, composedEnhancers); export type AppDispatch = ThunkDispatch<RootState, undefined, Action>; // app/actions/index.ts import type { Dispatch } from '@reduxjs/toolkit' import { SET_POKEMONS } from '~/types/actionTypes'; import type { PokeDetail, PokemonDataType } from '~/types/pokeTypes'; import { getPokemonDetails } from '~/api/getPokemonDetails'; export const setPokemons = (payload: PokeDetail[]) => ({ type: SET_POKEMONS, payload, }); export const getPokemonsWithDetails = (pokemons: PokemonDataType[] = []) => async (dispatch: Dispatch) => { if (pokemons.length > 0) { const pokeDetailsRaw = await Promise.all( pokemons.map((poke: PokemonDataType) => getPokemonDetails(poke)), ); const pokeDetails = pokeDetailsRaw.filter( (poke): poke is PokeDetail => poke !== undefined, ); dispatch(setPokemons(pokeDetails)); } }; // app/welcome/welcome.tsx import { useEffect } from 'react'; import { Col } from 'antd'; import { useDispatch, useSelector } from 'react-redux'; import logo from '../statics/logo.svg'; import { PokeList } from '~/components/PokeList'; import { Searcher } from '~/components/Searcher'; import { getPokemon } from '~/api/getPokemon'; import { getPokemonDetails } from '~/api/getPokemonDetails'; import { type RootState, type AppDispatch } from '~/store/store'; import { getPokemonsWithDetails } from '~/actions'; export function Welcome() { const pokemons = useSelector( (state: RootState) => state.pokemonData.pokemons, ); const dispatch = useDispatch<AppDispatch>(); useEffect(() => { const fetchPokemons = async () => { const pokeRes = await getPokemon(); if (pokeRes) { dispatch(getPokemonsWithDetails(pokeRes)); } }; fetchPokemons(); }, [getPokemon, getPokemonsWithDetails, getPokemonDetails]); return ( <main> <h1>Pokedux</h1> <Col span={4} offset={10} className='my-3'> <img src={logo} alt='Pokedux' /> </Col> <Col span={8} offset={8} className='mt-3'> <Searcher /> </Col> {pokemons && <PokeList pokemons={pokemons} />} </main> ); }
import {createAsyncThunk} from "@reduxjs/toolkit"; import {pokemonApi} from "../../api"; const pokemonThunks = { all: createAsyncThunk('pokemon/all', async (limit: number) => { const {results} = await pokemonApi.all(limit) return await Promise.all(results.map(({url}) => pokemonApi.byPokemon(url))) }), pokemonDetails: createAsyncThunk('pokemon/byName', async (url: string) => await pokemonApi.byPokemon(url)) } export {pokemonThunks}
Me sale este error Uncaught (in promise) Error: Actions must be plain objects. Use custom middleware for async actions.
Mi App:
import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import {Col} from 'antd'; import {Searcher, PokemonCard} from './components'; import {getPokemons} from './api'; import { getPokemonsWithDetails } from './actions'; import { setPokemons } from './actions'; function App({}) { const pokemons = useSelector(state => state.pokemons); const dispatch = useDispatch(); const getData = async () => { //Recibimos los pokemones const payloadData = await getPokemons(); //Disparamos nuestra acción de tipo setPokemons y pasamos como payload la data (los pokemones) dispatch(getPokemonsWithDetails(payloadData)); }; useEffect(() => { getData(); }, []); return ( <div className="container"> <Col span={8} offset={8}> <Searcher /> </Col> <Col span={8} className='pokemons-container'> <PokemonCard pokemons={pokemons} /> </Col> </div> ) }; export default App;
El action creator:
export const getPokemonsWithDetails = (pokemons = []) => async (dispatch) => { console.log(pokemons) const pokemonsDetailed = await Promise.all( pokemons.map(async pokemon => await getPokemonDetails(pokemon)) ); dispatch(setPokemons(pokemonsDetailed)) }
index:
import { legacy_createStore as createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import App from './App'; import { pokemonsReducer } from './reducers/pokemons'; import './index.css'; const composeAlt = window.__REDUX_DEVTOOLS_EXTENSION__ || compose; const composedEnhacers = composeAlt(applyMiddleware(thunk)); const store = createStore(pokemonsReducer, composedEnhacers); ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
me pasa lo mismo, pudiste solucionarlo?
Ya lo encontré, creo que en la clase se hizo distinto, o yo soy un despistado, pero chequea que cuando haces el dispatch en App.jsx sea a getPokemonsWithDetails y no a getPokemonDetails. Despues anda a la seccion de archivos de la clase y fijate que tengas bien escrito los archivos de api y de actions.
me sale este warning en la consola.y no me deja usar las dev tools. alguien tuvo el mismo problema? ++VM333 react_devtools_backend.js:4026 Application state or actions payloads are too large making Redux DevTools serialization slow and consuming a lot of memory. See https://git.io/fpcP5 on how to configure it.++
También yo
para mas coherencia no habria que poner tambien el fetchPokemons dentro del dispatch? asi estaria todo envuelto y no uno con dispatch y otro en otra parte pero se entiende que solo es la presentacion educativa del thunk
Fuerza fam!
Si hay frustración significa que estamos expandiendo límites 🧱
Branch name:
feat/redux-thunk
Commit message:
feat: implement redux-thunk for async actions
This commit introduces Redux Thunk to manage asynchronous actions within the application.
Changes include:
- Integrate redux-thunk middleware in to enable asynchronous action handling.
- Refactor useEffect hook to improve readability and leverage thunk actions for fetching Pokemon data.
- Implement thunk action in to handle asynchronous fetching of Pokemon details.
Puede ser que haya algunos cambios entre la versión de Redux Thunk del curso y la que tengo actualmente: Aquí algunos cambios que tuve que hacer para que funcione:
1) Un import nombrado
import { thunk } from "redux-thunk";
2) Asegurarme que el payload exista en mi middleware
este otro link (https://daveceddia.com/what-is-a-thunk/) explica muy bien que seria thunk desde su inicio