No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Redux Thunk

13/22
Recursos

Aportes 12

Preguntas 4

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

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;

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>
  );
}

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;

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)
}

React custom hooks es mejor para data fetching que redux thunk.
https://redux.js.org/style-guide/#use-thunks-for-async-logic

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'; `
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 ![](https://static.platzi.com/media/user_upload/image-8df371d9-dc3d-4f7f-a095-1d7ad75fb05b.jpg)
este otro link (https://daveceddia.com/what-is-a-thunk/) explica muy bien que seria thunk desde su inicio

Habla mal de platzi que ya no tenga un curso de programación funcional.