La cantidad de código que hay que escribir se ha incrementado mucho para algo sencillo. Pero seguro con la práctica y la repetición será más fácil de recordar los pasos a seguir.
Conceptos claves para empezar
¿Ya tomaste el Curso Básico de Redux?
Conceptos claves de Redux
Ciclo de vida de Redux
Diferencias entre Redux y Context
Introducción a nuestro proyecto
Creemos una Pokedux
Iniciando nuestro proyecto
¡Atraparlos ya!
Introducción a PokeAPI
React.js + Redux
Integrando Redux
Hooks vs. Connect
Redux DevTools
Middlewares
Middlewares
Peticiones asíncronas
Redux Thunk
Middlewares alternativos: Redux Saga
Avanzando la ui
Agreguemos un loader
Agreguemos favoritos
Inmutabilidad
¿Qué es inmutabilidad?
Agregando Inmutabilidad a nuestra Pokedux
Avanzado
Cuándo usar reducers combinados
Redux Toolkit: creando nuestro primer Slice
Redux Toolkit: createAsyncThunk
Despedida del curso
Conclusiones
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Mariangélica Useche
Aportes 9
Preguntas 0
La cantidad de código que hay que escribir se ha incrementado mucho para algo sencillo. Pero seguro con la práctica y la repetición será más fácil de recordar los pasos a seguir.
Muy bueno el curso, pero no me gusto ant design, yo sigo usando tailwind pero como se menciono al inicio solo es para maquetacion rápida.
también podemos colocar el dispatch de la acción que controla el loading directamente en la acción getPokemonWithDetails
export const getPokemonsWithDetails = (pokemons = [])=> async (dispatch)=> {
dispatch(setLoading(true))
const pokemonsDetailed = await Promise.all(pokemons.map(pokemon => getPokemonDetail(pokemon)))
dispatch(setPokemons(pokemonsDetailed))
dispatch(setLoading(false))
}
de esta forma, la misma acción controla la petición a la API y la carga del spinner, que siempre van de la mano uno con el otro, y nuestro useEffect del componente App quedaría mucho mas limpio
const App = ()=> {
...
useEffect(()=> {
const fetchPokemons = async ()=> {
const pokemonsRes = await getPokemon()
dispatch(getPokemonsWithDetails(pokemonsRes))
}
fetchPokemons()
}, [])
}
import {Col, Spin} from 'antd';
import Searcher from "./components/Searcher";
import {useEffect} from 'react'
import PokemonList from "./components/PokemonList.jsx";
import {getPokemon} from "./api";
import {getPokemonWithDetails, setLoadings} from "./actions/index.js";
import Logo from "./assets/logo.svg";
import './App.css'
import {useDispatch, useSelector} from "react-redux";
function App() {
const pokemons = useSelector(state => state.pokemons);
const loading = useSelector(state => state.loading);
const dispatch = useDispatch();
useEffect(() => {
const fetchPokemons = async () => {
dispatch(setLoadings(true))
const response = await getPokemon();
dispatch(getPokemonWithDetails(response));
dispatch(setLoadings(false))
}
fetchPokemons();
}, []);
return (
<div className="App">
<Col span={4} offset={10}>
<img src={Logo} alt="pokeapi"/>
</Col>
<Col span={8} offset={8}>
<Searcher/>
</Col>
{loading ? (
<Col offset={12} className="Spinner">
<Spin spinning size="large"/>
</Col>
) : (
<PokemonList pokemons={pokemons}/>
)}
</div>
)
}
export default App;
@import "antd/dist/antd.css";
.App{
margin-top: 3rem;
}
.App img {
margin-bottom: 3rem;
}
.App img {
margin-bottom: 3rem;
}
.Spinner {
margin-top: 3rem;
}
import {SET_LOADING,SET_POKEMON} from "../actions/types.js";
const initialState = {pokemons: [], loading: false};
export const pokemonsReducer = (state,action) => {
switch (action.type) {
caseSET_POKEMON:
return {...state, pokemons:action.payload}
break;
caseSET_LOADING:
return {...state, loading:action.payload}
break;
default:
return {...state};
break;
}
}
export constSET_POKEMON= 'SET_POKEMON';
export constSET_LOADING= 'SET_LOADING';
import {SET_POKEMON,SET_LOADING} from "./types";
import {getPokemonDetails} from "../api";
export const setPokemons = (payload) => ({
type:SET_POKEMON,
payload
});
export const setLoadings = (payload) => ({
type:SET_LOADING,
payload,
});
export const getPokemonWithDetails = (pokemons= []) => async (dispatch) => {
const pokemonsDetailed = awaitPromise.all(pokemons.map(pokemon=>getPokemonDetails(pokemon)));
dispatch(setPokemons(pokemonsDetailed));
};
Aporte en TypeScript de la clase
.
// src/actions/types.ts
export const SET_POKEMONS_DETAILS = "SET_POKEMONS_DETAILS";
export const SET_LOADING = "SET_LOADING";
// src/actions/index.ts
import { IPokemonDetails, IPokemonType } from "../types";
import { Dispatch } from "redux";
import { SET_LOADING, SET_POKEMONS_DETAILS } from "./types";
import { getPokemonDetails } from "../api";
export const setPokemonsDetails = (
payload: (IPokemonDetails | undefined)[]
) => ({
type: SET_POKEMONS_DETAILS,
payload,
});
export const setLoading = (payload: boolean) => ({
type: SET_LOADING,
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/reducers/pokemons.ts
import { SET_LOADING, SET_POKEMONS_DETAILS } from "../actions/types";
import { InitialPokemonsStateType } from "../types";
const initialState: InitialPokemonsStateType = {
pokemons: [],
loading: false,
};
export const pokemonsReducer = (
state: InitialPokemonsStateType = initialState,
action: any
) => {
switch (action.type) {
case SET_POKEMONS_DETAILS:
return {
...state,
pokemons: action.payload,
};
case SET_LOADING:
return {
...state,
loading: action.payload,
};
default:
return state;
}
};
.
// src/App.tsx
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Col, Spin } from "antd";
import Searcher from "./components/Searcher";
import PokemonList from "./components/PokemonList";
import { getPokemon } from "./api";
import { getPokemonsDetailsAction, setLoading } from "./actions";
import { InitialPokemonsStateType, 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);
const loading = useSelector(
(state: InitialPokemonsStateType) => state.loading
);
// Dispatch se debe tipar para que no ocurran inconvenientes con lo que devuelve
const dispatch = useDispatch<Dispatch<any>>();
useEffect(() => {
const fetchPokemons = async () => {
dispatch(setLoading(true));
const response = await getPokemon();
response && dispatch(getPokemonsDetailsAction(response));
dispatch(setLoading(false));
};
fetchPokemons();
}, [dispatch]);
return (
<div className="App">
<Col span={4} offset={10}>
<img src={logo} alt="Pokedux" />
</Col>
<Col span={8} offset={8}>
<Searcher />
</Col>
{loading ? (
<Col span={8} offset={8} style={{ marginTop: "2rem" }}>
<Spin spinning size="large" />
</Col>
) : (
<PokemonList pokemons={pokemons} />
)}
</div>
);
}
export default App;
.
// src/types/index.ts
export interface IPokemonDetails {
abilities: [];
name: string;
sprites: SpritesType;
}
export interface IPokemonType {
name: string;
url: string;
}
export type InitialPokemonsStateType = {
pokemons: IPokemonType[];
loading: boolean;
};
export type SpritesType = {
front_default: string;
};
Dejo mi código TS de la clase:
// App.tsx
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Search } from './components/Search'
import { PokemonList } from './components/PokemonList'
import { Col, Spin } from 'antd'
import { getPokemons } from './api'
import { getPokemonsWithDetails, setLoading, setPokemons } from './actions'
import { TypeState } from './reducers/pokemon'
import logo from './statics/logo.svg'
import './App.css'
function App() {
const pokemons = useSelector((state:TypeState)=>state.pokemons)
const loading = useSelector((state:TypeState)=>state.loading)
const dispath = useDispatch();
useEffect(()=>{
async function fetchPokemon(){
dispath(setLoading(true))
const pkmns = await getPokemons();
dispath(getPokemonsWithDetails(pkmns))
dispath(setLoading(false))
}
fetchPokemon();
},[])
return (
<div className="App">
<Col span={4} offset={10}>
<img src={logo} alt="Pokedux" />
</Col>
<Col span={8} offset={8}>
<Search />
</Col>
{loading?
<Col offset={12} >
<Spin spinning size='large' />
</Col>
:
<PokemonList pokemons={pokemons} />
}
</div>
)
}
export default App;
// action/type.ts
export const SET_POKEMONS = 'set_pokemons'
export const SET_LOADING = 'set_loading'
// action/index.ts
import { getPokemonDetails, PokemonDetailType, PokemonType } from "../api";
import { SET_LOADING, SET_POKEMONS } from "./types";
export const setPokemons = (payload: PokemonDetailType[])=>({
type: SET_POKEMONS,
payload
})
export const setLoading = (payload:boolean)=>({
type: SET_LOADING,
payload
})
export const getPokemonsWithDetails =
(pokemons:PokemonType[]=[]) =>
async (dispatch: any) =>{
const pkmnsDetailed = await Promise.all((pokemons as PokemonType[]).map(pkmn=>getPokemonDetails(pkmn)))
dispatch(setPokemons(pkmnsDetailed))
}
import { SET_LOADING, SET_POKEMONS } from "../actions/types"
import { PokemonDetailType } from "../api"
export type TypeState = {
pokemons: PokemonDetailType[],
loading : boolean
}
const initialState: TypeState = {
pokemons: [],
loading: false
}
export const pokemonReducer = (state:TypeState = initialState, action:any)=>{
switch(action.type){
case SET_POKEMONS:
return {...state, pokemons: action.payload}
case SET_LOADING:
return {...state, loading: action.payload}
default:
return state
}
}
para una mejor experiencia de usuario considero que seria mejor un loader squeleton
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?