No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Agreguemos favoritos

16/22
Recursos

Aportes 13

Preguntas 3

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

En el video creo que hay un corte, donde pasan la prop: favorite al componente PokemonCard, y en este se lo pasan al Card, se lo pasan al isFavorite, este prop le llega a PokemonCard desde PokemonList como favorite={pokemon.favorite}

import { useDispatch } from 'react-redux';
import { Card } from 'antd';
import Meta  from 'antd/lib/card/Meta';
import StarButton from './StarButton';
import { setFavorite } from '../actions/index'

const PokemonCard = ({ name, image, abilities, id, favorite }) => {
const dispatch = useDispatch();
const allAbilities = abilities.map(ability => ability.ability.name).join(', ');

const handleOnFavorite = () => {
    dispatch(setFavorite({pokemonId: id}));
}
    return (
    <Card
        title={name}
        cover={<img src={image} alt={name} />}
        extra={<StarButton isFavorite={favorite} onClick = {handleOnFavorite}/>}
    >
        <Meta description={allAbilities}/>
    </Card>)
}

export default PokemonCard;

en el minuto 15:41 al parecer hay un peque帽o corte, si al terminar la clase falla o se rompe la aplicaci贸n, lo 煤nico que debemos hacer es pasar favorite como props desde el componente PokemonList al componente PokemonCard

const PokemonList = ({ pokemons })=> {
    console.log(pokemons)


    return (
        <div className="PokemonList">
            {
                pokemons.map(pokemon => (
                    <PokemonCard 
                        name={pokemon.name} 
                        key={pokemon.name} 
                        image={pokemon.sprites.front_default} 
                        types={pokemon.types} 
                        id={pokemon.id}
                        favorite={pokemon.favorite} // **
                    />
                ))
            }
        </div>
    )
}

luego debemos recibir esa prop en el componente PokemonCard y pasarla como valor de la prop isFavorite que PokemonCard le pasa a el componente StarButton

const PokemonCard = ({ name, image, types, id, favorite })=> {
    const dispatch = useDispatch()

    const handleOnFavorite = ()=> {
        dispatch(setFavorite({ pokemonId: id}))
    }

    return (
        <Card 
            ..... 
            extra={<StarButton isFavorite={favorite} onClick={handleOnFavorite} />}
        >
        </Card>
    )
}

de esta forma la aplicaci贸n no se romper谩 y tendr铆amos el c贸digo igual al de la clase, debemos saber que la API no contiene esta propiedad en un inicio, cada objeto pokemon no contiene esta propiedad favorite por defecto, pero el c贸digo funciona y la raz贸n es que, en un inicio cuando pasamos la prop favorite desde el componente PokemonList al componente PokemonCard, el valor de esta prop es undefined, no esta definido aun, cuando la recibimos en PokemonCard y pasamos como valor de la prop isFavorite a el componente StarButton sigue siendo undefined, este componente StarButton la recibe y hace una validaci贸n con esta propiedad isFavorite, al ser su valor undefined renderizara el componente StarOutLined

const StarButton = ({ isFavorite, onClick })=> {
    const Icon = isFavorite ? StarFilled : StarOutlined
    
    return <Button icon={<Icon/>} onClick={onClick}>

    </Button>
}

esta propiedad favorite se a帽ade a los objetos pokemon cuando se dispara la acci贸n setFavorite y llega al reducer

export const pokemonsReducer = (state = initialState, action)=> {
    switch(action.type){
        case SET_POKEMONS:
            .... 
        case SET_FAVORITE:
            const newPokemonList = [ ...state.pokemons ]
            const pokemonIndex = newPokemonList.findIndex(pokemon => pokemon.id === action.payload.pokemonId)

            if (pokemonIndex < 0){
                return state
            }

            newPokemonList[pokemonIndex].favorite = !newPokemonList[pokemonIndex].favorite 
            return { ...state, pokemons: newPokemonList }
        case SET_LOADING:
                ....
        default: 
            return state 
    }
}

este caso del reducer lo que hace es crear la nueva propiedad favorite en el objeto pokemon y darle como valor lo opuesto al valor que tiene, como acabamos de crear la propiedad y no le hemos dado un valor aun, su valor por defecto es undefined y al darle como valor lo opuesto a undefined su valor al final seria true

console.log(!undefined) // true 

de esta manera hemos creado la propiedad favorite con su valor en true en el objeto pokemon al que demos click, y al dar click nuevamente su valor ser谩 lo opuesto, es decir de true a false y as铆 cada vez que demos click. Analizando un poco el flujo de la data, pude notar que estar铆amos creando esta propiedad solo en algunos objetos pokemon, al final tendriamos objetos pokemon que tienen una propiedad favorite y otros que no, lo cual puede generarnos conflictos en un futuro, para evitar esto lo hice de otra forma, no paso la propiedad favorite como prop de PokemonList a PokemonCard

const PokemonList = ({ pokemons })=> {
    console.log(pokemons)


    return (
        <div className="PokemonList">
            {
                pokemons.map(pokemon => (
                    <PokemonCard 
                        name={pokemon.name} 
                        key={pokemon.name} 
                        image={pokemon.sprites.front_default} 
                        types={pokemon.types} 
                        id={pokemon.id}
                    />
                ))
            }
        </div>
    )
}

por lo tanto, no la recibo en el componente PokemonCard, lo que hago es crear un estado local con valor inicial en false

const [favoriteLocal, setFavoriteLocal] = useState(false)

se lo paso como valor de la prop isFavorite a el componente StarButton, este componente StarButton al recibir la acci贸n de click dispara la acci贸n setFavorite la cual no recibir谩 un objeto {pokemonId: id}, solamente id, y cambiara el valor del estado local a su opuesto con setFavoriteLocal

const PokemonCard = ({ name, image, types, id })=> {
    const dispatch = useDispatch()
    const [favoriteLocal, setFavoriteLocal] = useState(false)

    const handleOnFavorite = ()=> {
        dispatch(setFavorite(id))
        setFavoriteLocal(!favoriteLocal)
    }

    return (
        <Card 
            style={{width: 250}} 
            title={name} 
            cover={<img src={image} alt={name} />} 
            extra={<StarButton isFavorite={favoriteLocal} onClick={handleOnFavorite} />}
        >
            ....
        </Card>
    )
}

sabemos que el componente StarButton esta recibiendo esa prop y haciendo una validaci贸n con ella, cuando el valor de esta prop sea true renderizara el componente StarFilled, de esta manera solucionar铆amos el problema del renderizado y al dar click en las card se estar谩 llenando y vaciando la estrella, pero el cambio mas grande esta en la acci贸n que se disparo cuando llega al reducer, debemos a帽adir una nueva propiedad favorite con valor de un array vac铆o al estado inicial

const initialState = {
    pokemons: [],
    loading: false,
    favorite: [],
}

una vez a帽adida la nueva propiedad solo debemos modificar el caso SET_FAVORITE, lo que este caso har谩 ser谩 a帽adir el objeto pokemon al que demos click al array de favoritos, y en caso de que ya exista, lo eliminara del arreglo

export const pokemonsReducer = (state = initialState, action)=> {
    switch(action.type){
        case SET_POKEMONS:
            ....
        case SET_FAVORITE:
            const x = state.pokemons.find(element => element.id == action.payload)
            const element = state.favorite.find(element => element.id == action.payload)

            if (element === undefined){
                return { ...state, favorite: [...state.favorite, x ]} 
            } else {
                const filter = state.favorite.filter(elem => elem.id !== action.payload )
                return { ...state, favorite: [ ...filter ] }
         }
        case SET_LOADING:
                ....
        default: 
            return state 
    }
} 

de esta manera, al dar click sobre un card de un pokemon se a帽adir谩 ese objeto al arreglo de favoritos y su estrella se llenara, al dar click nuevamente se sacara del arreglo y su estrella se vaciara, al final tendr铆amos un arreglo de pokemons con todos nuestros objetos pokemon intactos y otro arreglo con solo nuestros objetos pokemon favoritos

Va quedando genial, a qui茅n m谩s le gust贸 ?

luego de terminar el video, deben pasarle la prop favorite al pokemonCard.jsx desde el pokemonList.jsx

favorite={pokemon.favorite}

y luego del pokemonCard.jsx pasarle esa misma prop al StarButton.jsx

extra={<StarButton isFavorite={favorite} onClick={handleOnFavorite} />}

Problemas de rendimiento

Hasta este punto se ha creado este proyecto dejando de lado un tema muy importante: Performance.
Tal cual ha avanzado el proyecto hasta este punto, en cada momento en el que se agrega un pokemon como favorito, esto causa que toda nuestra lista de pokemones se renderice totalmente.

Si de toda la lista de pokemones (digamos que pueden ser miles), solo cambia el valor isFavorite de 1 solo. 驴Es coherente que se rendericen TODOS los pokemones?

Pues la respuesta es NO!!! Y aqu铆 te dejo una peque帽a soluci贸n.

Para esto utilizaremos React.memo(), esta funci贸n nos permite guardar en memoria el estado renderizado de un componente y hacer una evaluaci贸n que b谩sicamente es lo siguiente: 驴Las props del componente cambiaron desde el 煤ltimo renderizado?

Si la respuesta es no, pues React no vuelve a renderizar el componente, tan solo muestra el que ten铆a guardado. Si la respuesta es s铆, React vuelve a renderizar el componente.

Ojo React.memo tiene muchas mas implicaciones y te recomiendo leer la documentaci贸n para saber cuando utilizarlo.

Entonces, esto parece que soluciona nuestro problema.

Pues lo 煤nico que debemos hacer es envolver nuestro componente PokemonCard dentro de este HOC:

export default React.memo(PokemonCard, (prevProps, nextProps) => {
  return prevProps.isFavorite === nextProps.isFavorite
});

De esta forma evitaremos renderizados innecesarios y cuando se agregue un pokemon a favoritos, solo se re-renderizar谩 ese card, no toda la lista.

Saludos,

Dejo mi codigo del proyecto en TS con Vite:

// PokemonList.tsx
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 
                    id={pokemon.id}
                    name={pokemon.name} 
                    types={pokemon.types} 
                    image={pokemon.sprites.other.home.front_default}  
                    key={pokemon.name} 
                    isFavorite={pokemon.favorite}
                />
            ))}
        </div>
    )
}

PokemonList.defaultProps = {
    pokemons: Array(10).fill('')
}

export {
    PokemonList
}
// PokemonCard.tsx
import { FC } from "react";
import { Card } from "antd";
import Meta from "antd/lib/card/Meta";
import { StarButton } from "./StarButton";
import { useDispatch, useSelector } from "react-redux";
import { setFavorite } from "../actions";

type Props = {
    name: string,
    image: string,
    types: any[],
    id: number,
    isFavorite: boolean
}

const PokemonCard:FC<Props> = ({name, image, types, id, isFavorite}) => {
    const dispath = useDispatch();
    return (
        <Card
            title={name}
            cover={<img src={image} alt={name} />}
            extra={<StarButton isFavorite={isFavorite} onClick={()=>{
                dispath(setFavorite({pokemonId: id}))
            }} />}
        >
            <Meta description={types.map(type=>type.type.name).join()} />
        </Card>
    )
}

export {
    PokemonCard
}
// StarButton.tsx
import { StarFilled, StarOutlined } from "@ant-design/icons";
import { Button } from "antd";
import { FC } from "react";

type Props = {
    isFavorite: boolean,
    onClick : ()=>void
}

const StarButton:FC<Props> = ({ isFavorite, onClick}) =>{
    const Icon = isFavorite?< StarFilled />:<StarOutlined />
    return <Button icon={Icon} onClick={onClick}></Button>
}

export {
    StarButton
}
// api/index.ts
import axios from "axios";

export type PokemonType = {
    name: string;
    url: string
}

export type PokemonDetailType = {
    id:number;
    name: string;
    image : string;
    types: any[];
    sprites: any;
    abilities: any;
    favorite: boolean
}

type GetPokemonsResponse = {
    results: PokemonType[]
}

type GetPokemonsDetailResponse = PokemonDetailType

export const getPokemons = async ()=>{
    try {
        const { data } = await axios.get<GetPokemonsResponse>('https://pokeapi.co/api/v2/pokemon?limit=151');
        return data.results;
    } catch (err) {
        console.error(err);
    }
}

export const getPokemonDetails = async (pokemon:PokemonType) => {
    const { data } = await axios.get<GetPokemonsDetailResponse>(pokemon.url);
    return data;
}
// actions/index.ts
import { getPokemonDetails, PokemonDetailType, PokemonType } from "../api";
import { SET_FAVORITE, 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 setFavorite = (payload:any)=>({
    type: SET_FAVORITE,
    payload
})

export const getPokemonsWithDetails = 
(pokemons:PokemonType[]=[]) => 
async (dispatch: any) =>{
    const pkmnsDetailed = await Promise.all((pokemons as PokemonType[]).map(pkmn=>getPokemonDetails(pkmn)))
    dispatch(setPokemons(pkmnsDetailed))
}
//reducers/pokemon.ts
import { SET_FAVORITE, 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_FAVORITE:
            const newPokemonList =  [...state.pokemons]
            const currentPokemonIndex = newPokemonList.findIndex(
                pkmn => {
                    return action.payload.pokemonId === pkmn.id  
                }
            )
            if(currentPokemonIndex < 0){
                return state;
            }
            newPokemonList[currentPokemonIndex].favorite = !newPokemonList[currentPokemonIndex].favorite
            return {...state, pokemons: newPokemonList}
        case SET_LOADING:
            return {...state, loading: action.payload}
        default:
            return state
    }

}

Se me ocurrio hacer el cambio de otra forma

case SET_FAVORITE:
      const pokemonsState = [...state.pokemons];
      const newValues = pokemonsState.map((element) =>
        element.id === action.payload
          ? { ...element, isFavorite: !element.isFavorite }
          : element
      );

      return { ...state, pokemons: newValues };

    case SET_LOADING:
      return { ...state, loading: action.payload };
Yo lo he hecho de forma diferentes, a los `switch/cases`, me parece un poco m谩s simple de llevar usando un poco de los conceptos que s茅 de Vuex, por lo que me hice un archivo aparte para las "`mutations`" que es lo que ser铆a b谩sicamente los favoritos ```js // reducer/mutations.js const markAsFavorite = (pokemonList, payload) => { const newPokemonList = [...pokemonList]; const currentPokemonIndex = newPokemonList.findIndex( (pokemon) => pokemon.id === payload.pokemonId, ); if (currentPokemonIndex < 0) { return pokemonList; } else { newPokemonList[currentPokemonIndex].favorite = !newPokemonList[currentPokemonIndex].favorite; return newPokemonList; } }; export { markAsFavorite }; ```Luego, esa funci贸n la llamo dentro de un ObjectMapper y queda tal que as铆: ```js // reducer/index.js import { markAsFavorite } from "./mutations"; const initialState = { loading: false, pokemons: [], }; const actionType = { setPokemon: "SET_POKEMON", setLoading: "SET_LOADING", setFavorite: "SET_FAVORITE", }; const reducerObject = (state, payload) => ({ [actionType.setPokemon]: { ...state, pokemons: payload, }, [actionType.setLoading]: { ...state, loading: payload, }, [actionType.setFavorite]: { ...state, pokemons: markAsFavorite(state.pokemons, payload), }, }); const reducer = (state = initialState, action) => reducerObject(state, action.payload)[action.type] || state; export { actionType, reducer }; ```Otra cosita que hice para lo del corte por fallos de edici贸n fue meter el favorite dentro de la query ```js // api/index.js import axios from 'axios'; const API = 'https://pokeapi.co/api/v2/pokemon'; const getPokemon = () => axios .get(`${API}?limit=11&offset=0`) // Must be 151 .then((res) => res.data.results) .catch((error) => console.log(error)); const getDetails = (pokemon) => axios .get(pokemon.url) .then((res) => { return { ...res.data, favorite: false, // Esto soluciona el problema del corte }; }) .catch((error) => console.log(error)); export { getPokemon, getDetails }; ```Luego de eso fue un cambio chico en la pokedex y ya quedo ```jsx // components/Pokedex.jsx // ... const Pokedex = ({ pokemons }) => { let pokemonData; const pokeList = pokemons.map((pokemon, index) => { pokemonData = { // ... id: pokemon.id, favorite: pokemon.favorite, // ... }; return <PokeCard key={pokemon.name} pokemonData={pokemonData} />; }); return
{pokeList}
; }; // ... ```
No eran las habilidades? 馃憖 馃憖 馃憖 馃憖

Lo mejor para estos casos es tener un array de favoritos y persistir ese array en el localstorage.

Espero que otro profesor dicte otro curso de React-Redux, porq a esta profesora le falta pedagogia y esta bastante mal todo el curso

La manera m谩s sencilla de solucionar el problema del 鈥渃orte鈥 de la clase es agregando a todos los pokemons la propiedad favorite. En las actions, especificamente en getPokemonsWithDetails, pueden hacer lo siguiente:

export const getPokemonsWithDetails =
    (pokemons = []) =>
    async (dispatch) => {
        const pokeDetail = await Promise.all(
            pokemons.map((p) => getDetailedPokemons(p))
        );
        const pokeDetailWithFav = pokeDetail.map((p) => {
            p.favorite = false;
            return {
                ...p,
            };
        });
        dispatch(setPokemons(pokeDetailWithFav));
    };

Y as铆 les quedar铆a la Card del Pokemon:

import { Card } from "antd";
import Meta from "antd/es/card/Meta";
import StarButton from "../StarButton/StarButton";
import { useDispatch } from "react-redux";
import { setFavorite } from "../../actions";
const PokeCard = ({ poke }) => {
    const dispatch = useDispatch();
    const capitalize = (word) => {
        return word.charAt(0).toUpperCase() + word.slice(1);
    };

    const pokeTypes = poke.types.map((t) => t.type);
    const typeNames = pokeTypes.map((t) => capitalize(t.name)).join(" - ");

    const handleClick = () => {
        dispatch(setFavorite({ pokeId: poke.id }));
    };
    return (
        <Card
            title={capitalize(poke.name)}
            cover={<img src={poke.sprites.front_default} alt={poke.name}></img>}
            extra={
                <StarButton isFavorite={poke.favorite} onClick={handleClick} />
            }
        >
            <Meta description={typeNames} />
        </Card>
    );
};

export default PokeCard;

Espero le sirva a alguien!

Cuando la profe hizo esto:

pokemonList[pokemonId].favorite = !pokemonList[pokemonId].favorite;

Dije: 隆隆Que trucazo!!

Yo hab铆a pensado agregar ids o nombres de pokemons en otro array donde se guardaran los favoritos para luego consultarlos en ese array seg煤n la l贸gica que quisiera hacer despu茅s, pero me gust贸 esta soluci贸n. En mi caso como uso un objecto reductor, mi l贸gica qued贸 as铆:

Esto es en el reducer.js

//Objeto reductor
const objectReducer = (state, payload) => ({
    [actionType.setPokemons] : {
        ...state,
        pokemons: payload,
    },
    [actionType.setLoading] : {
        ...state,
        loading: payload,
    },
    [actionType.setAddedToFavorites] : {
        ...state,
        pokemons: payload,
    }
})
//Funci贸n reductora la cual devuelve el estado actualizado
function pokemonsReducer(state = initialState, action){
    return objectReducer(state, action.payload)[action.type] || state;
}

Esto es en el componente del button

import React from 'react';
import { HiStar, HiOutlineStar } from "react-icons/hi2";
import { useDispatch, useSelector } from 'react-redux';
import { handleSetAddedToFavorite } from '../utils/pokemonRedux';

function FavoriteIcon({pokemon}){
    const pokemonState = useSelector(state => state.pokemons);
    const dispatch = useDispatch();
    const Icon = pokemon.favorite ? HiStar : HiOutlineStar;

    //Agregar o eliminar de favoritos
    const handleAddToFavorite = () => { 
        const pokemonList = [...pokemonState];
        const pokemonId = pokemonList.findIndex((item)=>{
             return item.id == pokemon.id;
        })
        pokemonList[pokemonId].favorite = !pokemonList[pokemonId].favorite;

        dispatch(handleSetAddedToFavorite(pokemonList))
    }

    return(
        <button className='pe-2' onClick={handleAddToFavorite}>
           <Icon/>
        </button>
    )
}