Adjunto mi respuesta
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 35
Preguntas 1
Adjunto mi respuesta
Seria un poco mejor pasar la logica de consulta de los detalles a al componente de PokemonCard. Eso evitaria el uso de Promise.all() el paso excesivo de parametros del componente padre al hijo y ademas seria un poco mas eficiente
Adjunto mi respuesta ingresando todos los datos y haciendo uso del método join
Es un metodo estatico de la clase Promise que toma como input un array de promesas y devuelve como resultado un array con los resultados de las promesas resueltas en el orden en que fueron agregadas en el input.
Devuelve los resultados una vez todas las promesas sean resueltas. Si una de ellas falla el Promise.all rechazara la operación.
Este ejemplo lo saque del confiable MDN Web Docs, por lo que si quieres una explicación más a fondo ve para alla:
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
Para el desafío, desde el componente PokemonList pase las habilidades por props a el componente PokemonCard
const PokemonList = ({ pokemons })=> {
return (
<div className="PokemonList">
{
pokemons.map(pokemon => (
<PokemonCard name={pokemon.name} key={pokemon.name} image={pokemon.sprites.front_default} abilities={pokemon.abilities} /> // **
))
}
</div>
)
}
desde el componente PokemonCard recibí y desestructure todas las props, al ser un arreglo lo que recibe en la prop de las habilidades, cree la lógica para poder iterar sobre cada elemento de este arreglo y presentar el nombre de cada habilidad en forma de lista de manera dinámica
const PokemonCard = ({ name, image, abilities })=> {
return (
<Card
....
>
<Meta description={<ul>
{abilities.map(ability => <li key={ability.ability.name} >{ability.ability.name}</li>)}
</ul>}
/>
</Card>
)
}
este fue el resultado:
Mi aporte con el reto de las habilidades y también agregue los tipos. Ademas agregue una etiqueta de color por cada tipo de pokemon.
.
.
PokemonCard
import React from 'react'
import { Card, Badge } from 'flowbite-react';
import { FiHeart } from "react-icons/fi";
function PokemonCard(props) {
const typeToColorClass = {
bug: "bg-lime-100 text-lime-900 dark:bg-lime-900 dark:text-lime-300",
dark: "bg-gray-200 text-white dark:bg-gray-400 dark:text-gray-900",
dragon: "bg-indigo-200 text-indigo-900 dark:bg-indigo-700 dark:text-indigo-200",
electric: "bg-yellow-200 text-black dark:bg-yellow-600 dark:text-yellow-100",
fairy: "bg-pink-200 text-pink-900 dark:bg-pink-800 dark:text-pink-100",
fighting: "bg-red-400 text-white dark:bg-red-600 dark:text-red-100",
fire: "bg-red-200 text-red-800 dark:bg-red-900 dark:text-red-100",
flying: "bg-teal-100 text-teal-700 dark:bg-teal-700 dark:text-teal-100",
ghost: "bg-purple-300 text-white dark:bg-purple-700 dark:text-purple-200",
grass: "bg-green-100 text-green-800 dark:bg-green-700 dark:text-green-200",
ground: "bg-amber-900 text-white dark:bg-amber-600 dark:text-brown-100",
ice: "bg-cyan-100 text-cyan-900 dark:bg-lightblue-700 dark:text-lightblue-100",
normal: "bg-gray-300 text-black dark:bg-gray-500 dark:text-gray-100",
poison: "bg-purple-200 text-purple-900 dark:bg-purple-700 dark:text-purple-200",
psychic: "bg-pink-200 text-pink-900 dark:bg-pink-600 dark:text-pink-100",
rock: "bg-slate-300 text-slate-900 dark:bg-amber-500 dark:text-brown-100",
steel: "bg-gray-400 text-black dark:bg-gray-600 dark:text-gray-200",
water: "bg-blue-200 text-blue-900 dark:bg-blue-800 dark:text-blue-100"
};
return (
<Card
className=''
imgAlt={props.name}
imgSrc={props.image}
>
<h1 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
{`${props.index} - ${props.name}`}
</h1>
<Badge className="bg-red-100 text-red-800 text-xs font-bold mr-2 px-2.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300">
ID: {props.id}
</Badge>
<h2>Type: </h2>
<p>
{props.types.map(object => {
const colorClass = typeToColorClass[object.type.name] || typeToColorClass.default;
return (
<span
key={object.type.name}
className={`text-md font-bold mr-2 px-3.5 py-2 rounded ${colorClass}`}
>
{object.type.name}
</span>
)
})}
</p>
<h3 className='font-bold'>Abilities: </h3>
<p className="font-normal text-gray-700 dark:text-gray-400">
{props.abilities.map(object => (
<li
key={object.ability.name}
> {object.ability.name} </li>
)
)}
</p>
<button>
<FiHeart/>
</button>
</Card>
)
}
export { PokemonCard }
PokemonList.jsx
import React from 'react'
import { PokemonCard } from '../PokemonCard'
import { v4 as uuidv4 } from 'uuid';
function PokemonList({ pokemons }) {
// console.log('file: PokemonList.jsx | Line 8: ',pokemons)
return (
<div className='PokemonList grid grid-cols-4 gap-4 flex-col w-full m-10 '>
{pokemons.map((pokemon, index) => {
const id = uuidv4();
return <PokemonCard
key={index}
id={id}
index={index}
name={pokemon.name}
image={pokemon.sprites.front_default}
abilities={pokemon.abilities}
types={pokemon.types}
/>
})}
</div>
)
}
PokemonList.defaultProps = {
pokemons : Array(10).fill('') // [''],[''],[''], ...x10
}
export { PokemonList }
.
API
import axios from 'axios'
const API_URL = 'https://pokeapi.co/api/v2/pokemon?limit=151'
export const getPokemon = async () => {
return axios
.get(API_URL)
.then((res) => res.data.results)
.catch((err) => console.log(err))
}
export const getPokemonDetails = (pokemon) => {
return axios.get(pokemon.url)
.then(res => res.data)
.catch((err) => console.log(err))
}
LO LOGREEEEEEEE
Yo saqué los nombres de habilidades desde el componente PokelmonList
export default function PokemonList() {
const pokemons = useSelector(state => state.pokemons)
return (
<div className="PokemonList">
{pokemons.map((pokemon) => {
let abilities = pokemon.abilities.map(ability => ability.ability.name).join(', ')
return <PokemonCard
name={pokemon.name}
key={pokemon.name}
img={pokemon.sprites.front_default}
abilities={abilities} />
})}
</div>
)
}
pdta: si, ability.ability es absurdo, creo que muchas veces uno pierde mucho tiempo por no ver bien cómo vienen las propiedades de los objetos, que a veces son absurdas pero pues es lo que hay :'v
Ahí hago uso del useSelector que sería acceder al store del redux, porque sino estamos haciendo uso de los props entonces daría igual usar redux (para mi), no entiendo por qué la profe no lo hace :p
Esta clase estuvo algo desafiante:
Dejo mi código para otros viajeros que se atrevieron a seguir esta senda: Estoy dejando todo el codigo que tuve que modificar , por eso el numero de archivos.
import { FC } from "react";
import { PokemonDetailType } from "../api";
import { PokemonCard } from "./PokemonCard";
import './PokemonList.css'
interface Props {
pokemons: PokemonDetailType[]
}
const PokemonList:FC<Props> = ({ pokemons }) =>{
return (
<div className="PokemonList">
{pokemons.map((pokemon,index)=>(
<PokemonCard
name={pokemon.name}
types={pokemon.types}
image={pokemon.sprites.other.home.front_default}
key={pokemon.name}
/>
))}
</div>
)
}
PokemonList.defaultProps = {
pokemons: Array(10).fill('')
}
export {
PokemonList
}
import { FC } from "react";
import { StarOutlined } from "@ant-design/icons";
import { Card } from "antd";
import Meta from "antd/lib/card/Meta";
type Props = {
name: string,
image: string,
types: any[]
}
const PokemonCard:FC<Props> = ({name, image, types}) => {
return (
<Card
title={name}
cover={<img src={image} alt={name} />}
extra={<StarOutlined />}
>
<Meta description={types.map(type=>type.type.name).join()} />
</Card>
)
}
export {
PokemonCard
}
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Search } from './components/Search'
import { PokemonList } from './components/PokemonList'
import { Col } from 'antd'
import { getPokemonDetails, getPokemons, PokemonType } from './api'
import { 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 dispath = useDispatch();
useEffect(()=>{
async function fetchPokemon(){
const pkmns = await getPokemons();
const pkmnsDetailed = await Promise.all((pkmns as PokemonType[]).map(pkmn=>getPokemonDetails(pkmn)))
dispath(setPokemons(pkmnsDetailed))
}
fetchPokemon();
},[])
return (
<div className="App">
<Col span={4} offset={10}>
<img src={logo} alt="Pokedux" />
</Col>
<Col span={8} offset={8}>
<Search />
</Col>
<PokemonList pokemons={pokemons} />
</div>
)
}
export default App;
import { PokemonDetailType, PokemonType } from "../api";
import { SET_POKEMONS } from "./types";
export const setPokemons = (payload: PokemonDetailType[])=>({
type: SET_POKEMONS,
payload
})
import { PokemonDetailType, PokemonType } from "../api";
import { SET_POKEMONS } from "./types";
export const setPokemons = (payload: PokemonDetailType[])=>({
type: SET_POKEMONS,
payload
})
import { SET_POKEMONS } from "../actions/types"
import { PokemonDetailType } from "../api"
export type TypeState = {
pokemons: PokemonDetailType[]
}
const initialState: TypeState = {
pokemons: []
}
export const pokemonReducer = (state:TypeState = initialState, action:any)=>{
switch(action.type){
case SET_POKEMONS:
return {...state, pokemons: action.payload}
default:
return state
}
}
Mi seguimiento con TypeScript. He tenido que cambiar varias cosas dentro del código, pero quedó mucho mejor y entendible para todos. Recibo sus comentarios y espero que ayude a quién lo pueda necesitar.
.
// 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 { logger } from './middlewares';
import './index.css';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const composedEnhancers = compose(
applyMiddleware(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>
);
.
// 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, getPokemonDetails } from "./api";
import { setPokemonsDetails } from "./actions";
import { IPokemonDetails } from "./types";
import logo from "./statics/logo.svg";
import "./App.css";
type AppType = {
pokemons: IPokemonDetails[];
};
function App() {
const pokemons = useSelector((state: AppType) => state.pokemons);
const dispatch = useDispatch();
useEffect(() => {
const fetchPokemons = async () => {
const response = await getPokemon();
if (response) {
const pokemonsDetails = await Promise.all(
response.map((pokemon) => getPokemonDetails(pokemon))
);
dispatch(setPokemonsDetails(pokemonsDetails));
}
};
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;
.
// types/index.ts
export interface IPokemonDetails {
abilities: [];
// base_experience: number;
// forms: [];
// game_indices: [];
// height: number;
// held_items: any[];
// id: number;
// is_default: boolean;
// location_area_encounters: string;
// moves: [];
name: string;
// order: number;
// past_types: any[];
// species: [];
sprites: SpritesType;
// stats: [];
// types: [];
// weight: number;
}
export interface IPokemonType {
name: string;
url: string;
}
export type InitialPokemonsStateType = {
pokemons: IPokemonType[];
};
export type SpritesType = {
back_default: string;
back_female: string | null;
back_shiny: string;
back_shiny_female: string | null;
front_default: string;
front_female: string | null;
front_shiny: string;
front_shiny_female: string | null;
};
.
// api/responses/index.ts
import { IPokemonType } from "../../types";
export type PokemonResponseType = {
results: IPokemonType[];
};
// api/index.ts
import axios from "axios";
import { IPokemonDetails } 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: any) => {
try {
const { data } = await axios.get<IPokemonDetails>(pokemon.url);
return data;
} catch (err) {
console.error("err ".repeat(5), err);
}
};
.
// reducers/pokemons.ts
import { SET_POKEMONS_DETAILS } from "../actions/types";
import { InitialPokemonsStateType } from "../types";
const initialState: InitialPokemonsStateType = {
pokemons: [],
};
export const pokemonsReducer = (
state: InitialPokemonsStateType = initialState,
action: any
) => {
switch (action.type) {
case SET_POKEMONS_DETAILS:
return {
...state,
pokemons: action.payload,
};
default:
return state;
}
};
.
// components/PokemonList
import React from "react";
import { IPokemonDetails } from "../types";
import PokemonCard from "./PokemonCard";
import "./PokemonList.css";
type PokemonListProps = {
pokemons: (IPokemonDetails | undefined)[];
};
const PokemonList = (props: PokemonListProps) => {
return (
<div className="PokemonList">
{props.pokemons.map(
(pokemon) =>
pokemon && <PokemonCard key={pokemon.name} pokemon={pokemon} />
)}
</div>
);
};
export default PokemonList;
// components/pokemonCard.tsx
import React from "react";
import { Card } from "antd";
import Meta from "antd/lib/card/Meta";
import { StarOutlined } from "@ant-design/icons";
import { IPokemonDetails } from "../types";
type PokemonCardProps = {
pokemon: IPokemonDetails;
};
const PokemonCard = (props: PokemonCardProps) => {
return (
<Card
style={{ width: 250 }}
title={props.pokemon.name}
cover={<img src={props.pokemon.sprites.front_default} alt={props.pokemon.name} />}
extra={<StarOutlined />}
>
<Meta description="fire, magic" />
</Card>
);
};
export default PokemonCard;
Adjunto el ejercicio incluidas las habilidades de cada pokemon, como buena practica, es recomendable separar la lógica (reduce, maps, condicionales) de las vistas jsx en archivos separados comúnmente llamados customHooks
https://github.com/fsiatama/pokedux/tree/feature/redux-devtools
Comparto mi solucion:
https://github.com/fua94/Platzi-React-Redux-Pro/tree/06-async
Mi solución c:
PokemonList
<div className='PokemonList'>
{pokemons.map((pokemon) => {
const abilitiesArray = pokemon.types;
const abilities = abilitiesArray.map((ability) => ability.type.name);
return <PokemonCard name={pokemon.name} imagen={pokemon.sprites.front_default} abilities={abilities} key={pokemon.name} />;
})}
</div>
PokemonCard
<Meta description={abilities.length > 1 ? `${abilities[0]}, ${abilities[1]}` : abilities[0] } />
Si en PokemonList en lugar de poner image={pokemon.sprites.front_default} ponen:
image={pokemon.sprites.other["official-artwork"].front_default}
cambia la imagen a una no pixeleada
Es normal que la carga de todos los data se tarde, como se mejora el performance, para hacer una mejor UX.
Dejo mi solución por acá:
src>components>PokemonList.jsx
import React from 'react'
import PokemonCard from '../PokemonCard'
function PokemonList({ pokemons }) {
return (
<div className='PokemonList'>
{pokemons.map(
(pokemon)=>(<PokemonCard
name={pokemon.name}
key={pokemon.name}
image={pokemon.sprites.front_default}
abilities={pokemon.abilities}
/>)
)}
</div>
)
}
export default PokemonList
src>components>PokemonCard.jsx
import React from 'react'
import {Card} from 'antd'
import Meta from 'antd/lib/card/Meta'
import {StarOutlined} from '@ant-design/icons'
import './PokemonList/PokemonList.css'
function PokemonCard({ name, image, abilities }) {
return (
<Card
title={name}
cover={<img
src={image}
alt={name}
/>}
extra={<StarOutlined/>}
>
<Meta description={abilities
.filter(ability => (!ability.is_hidden))
.map(ability => (ability.ability.name))
.join(', ')
}
/>
</Card>
)
}
export default PokemonCard
Se está tomando en cuenta si la habilidad está oculta o no.
Para el que quiere ver como se hace la peticion utilizando las promesas.
useEffect(() => {
getPokemons()
.then((res) => res.results) // retorna el array de pokemons
.then((res) => {
// hacemos un promise All para que por cada pokemon haga una peticion al backend sobre sus detalles
Promise.all(
res.map((pokemon) =>
getPokemonDetails(pokemon.url).then((res) => res)
)
).then((res) => dispatch(setPokemons(res)));
// una vez terminadas todas nuestras promesas hacemos el dispatch
})
.catch((err) => dispatch(setError(err)));
}, []);
Mi solución fué:
import PokemonCard from "./PokemonCard";
const PokemonList = ({ pokemons }) => {
return (
<div className="PokemonList">
{pokemons.map((pokemon) => {
return (
<PokemonCard
name={pokemon.name}
key={pokemon.name}
image={pokemon.sprites.front_default}
abilities={pokemon.abilities[0].ability.name}
/>
);
})}
</div>
);
};
PokemonList.defaultProps = {
pokemons: Array(8).fill(""),
};
export default PokemonList;
No fue lo más óptimo, pero fue lo que me salió 😅
Al momento de traer la imagen de los pokemons, con este codigo traes un svg que se ve mas definido.
<div className="PokemonList">
{pokemons.map((pokemon, index) => {
return <PokemonCard key={id + index} name={pokemon.name} image={pokemon?.sprites?.other?.dream_world?.front_default}/>
})}
</div>
Hice esto pero con los tipos de pokemones:
archivo PokemonList.jsx
Hice una función q hace un map por cada abilities, retorna las habilidades separadas por un guion
const pokemonsAbilities = abilities.map(ability => ability.ability.name);
// utilizando ListFormat
const formatAbilities = new Intl.ListFormat('es').format(pokemonsAbilities);
// como estamos indicando el idioma en español('es') aparece
// la conjunción "y", si fuera inglés aparecería "and"
// output por cada pokemon(pidgeot) => keen-eye, tangled-feet y big-pecks
// output por cada pokemon(pidgeot) => keen-eye, tangled-feet, and big-pecks
<Meta description={formatAbilities} />
archivo PokemonList.jsx
pase las habilidades como props
archivo PokemonCard.jsx
Hice un map por cada habilidad dentro de abilities
asi me quedo finalmente, para poner la description, me ayude con el codigo de Fabian, porque no sabia como llegar hasta las abilidades por la parte del codigo
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?