No tienes acceso a esta clase

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

Conclusiones

22/22
Recursos

Aportes 41

Preguntas 3

Ordenar por:

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

o inicia sesi贸n.

AM脡. ESTE. CURSO. lo pondr茅 en pr谩ctica ya mismo en un proyecto que estoy haciendo para mi portafolio馃挌馃挌馃挌

Muchas gracias por el curso, excelente!

Para el buscador modifiqu茅 el initialState agreg谩ndole un campo llamado pokemonsFiltered. Tambi茅n modifiqu茅 el dataSlice, donde en setPokemons se cargan ambos campos y en setFavorite se lee pokemonsFiltered. Y agregu茅 la funci贸n setFiltered

const initialState = {
  pokemons: [],
  pokemonsFiltered: [],
};

/*..... */

export const dataSlice = createSlice({
  name: "data",
  initialState,
  reducers: {
    setPokemons: (state, action) => {
      state.pokemons = action.payload;
      state.pokemonsFiltered = action.payload;
    },
    setFavorite: (state, action) => {
      const currentPokemonIndex = state.pokemonsFiltered.findIndex(
        (pokemon) => pokemon.id === action.payload.pokemonId
      );
      if (currentPokemonIndex >= 0) {
        const isFavorite = state.pokemonsFiltered[currentPokemonIndex].favorite;
        state.pokemonsFiltered[currentPokemonIndex].favorite = !isFavorite;
      }
    },
    setFilter: (state, action) => {
        const pokemonsFiltered = state
            .pokemons
            .filter( pokemon => pokemon.name.includes( action.payload ) )
        state.pokemonsFiltered = pokemonsFiltered;
    }
  },
});

El componente App lee pokemonsFiltered

const pokemons = useSelector((state) => state.data.pokemonsFiltered, shallowEqual);

Finalmente, el componente Searcher dispara la funci贸n setFilter para modificar la lista.

export const Searcher = () => {

  const dispatch = useDispatch();

  const handleOnChange = (e) => {
    dispatch(setFilter(e.target.value));
  }

  return <Input.Search 
    placeholder="Buscar..." 
    onChange={handleOnChange}
    style={{marginBottom: '10px'}} />
}

Que importante es conocer el flujo de redux en versiones anteriores o 鈥渢he old way鈥, he aplicado redux toolkit en trabajo pero nunca la manera anterior a este. Muchas gracias por el curso!

Gran curso, rapido y conciso, que gran facilidad para explicar las cosas 鉂
esta es mi pokedex construida con react y tailwind
todavia no la termino, porque estaba con el tema del dise帽o, ya que soy pesimo en eso xD,
pero implemente una paginaci贸n simple 馃槂 , modo oscuro 馃寳 y un spinner de pokeball simulando la captura 馃槣

m谩s adelante implementar茅 rutras (wouter), detallles del pokemon y fetchs por generaciones

la captura de pantalla la realice con esta app 馃槂
WebScreenshots
en esta utilize jotai para el manejo del estado 馃槃

隆Qu茅 bien me encant贸 este curso!!

Comparto el resultado del proyecto final:
Repositorio: https://github.com/jhcueva/Pokedex
Web desplegada: https://pokedex-tau.vercel.app

Excelente curso! Hace falta un curso de Redux Toolkit puramente, donde profundicemos mas en su uso.

Por fin entendi Redux.

Gracias!!!

estuvo buenisimo el curso

Dejo mi solucion con estilos propios y el buscador funcional. Lo hice creando un nuevo metodo en el slice de data mas otro estado que verifica el estado del imput y en base a eso actualiza la UI en caso que no encuentre ningun resultado

web deployed: https://pokedux-delegadodev.netlify.app/
codigo: https://github.com/Delegado007/pokedux/tree/styles_component

As铆 qued贸 mi sitio con la funci贸n de buscar

https://eduardxmartinez.github.io/pokedux/

Me gusto mucho el curso estuvo genial, debo leer la Doc de Redux para realmente sacarle provecho a todo lo que tiene.

Comparto mi PokeRedux final, agregu茅 la vista de detalles y el men煤 de favoritos(la estrella grande amarilla), las b煤squedas filtran tipos de pokemon y nombres de pokemon, se puede consultar pokemons por regiones. El dise帽o lo realic茅 con tailwind y algunas im谩genes son de PNGEggs y WikiDex.
Page: https://kortexcode.github.io/Pokedex-Redux/
Repo: https://github.com/KortexCode/Pokedex-Redux

Teacher 5 stars !!

Excelente curso 馃槃

Este curso estuvo buen铆simo, sin duda alguna lo mejor de lo mejor, muchas gracias profe.

Excelente curso y que gran profesora, muy bien explicado todo

Tan bueno el curso que me lo devor茅 en una tarde 鈾 me servir谩 much铆simo

Tom贸 su tiempo, la ultima parte, pero finalmente se consigui贸. Quer铆a mas de una vez hacerlo sin redux por lo peque帽o de la app 馃槃

Lo que hice para evitar tener un doble listado de elementos fue en principio en nuestra funci贸n fetchPokemonWithDetails() hice un ciclo adicional el cual solo corre la primera vez para agregar una propiedad adicional llamada visible la cual por defecto estar谩 en true para que se muestren todos los pokemons en la primera carga

export const fetchPokemonWithDetails = createAsyncThunk(
    "data/fetchPokemonWithDetails",
    async (_, {dispatch}) => {
        dispatch( setLoading(true));
        const pokemonsResponse = await getPokemons();
        const pokemonsDetailed = await Promise.all( pokemonsResponse.map( (pokemon) => getPokemonDetails(pokemon) ));

        pokemonsDetailed.forEach( (pokemon) => {
            pokemon.visible = true;
        });

        dispatch( setPokemons(pokemonsDetailed) );
        dispatch( setLoading(false));
    }
);

Esta propiedad la envio desde PokemonList a PokmeonCard

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

En PokmeonCard creo una l贸gica sencilla para mostrar o no mostrar el card dependiendo lo que venga en visible

return visible ? <Card 
        title={name}
        cover={<img src={image} alt="Charmander" />}
        extra={<StarButton isFavorite={favorite} onClick={ handleOnFavorite } />}
    >
        <Meta description={typesString} />
    </Card> : null

En el reducer de data creo la l贸gica para tener el estado del valor buscado, para lo cual creo una variable llamada searchedValue en. el initialState, y agrego el respectivo reducer que se encarga de procesar la l贸gica para activar o no el visible basado en el contenido del estado de searchedValue

setSearchedValue: (state, action) => {
            state.searchedValue = action.payload;

            if( state.searchedValue !== "" ) {
                state.pokemons.forEach( (pokemon) => {
                    pokemon.visible = pokemon.name.toLowerCase().includes(state.searchedValue.toLowerCase())
                });
            }
            else {
                state.pokemons.forEach( (pokemon) => pokemon.visible = true )
            }
        }

Pongo todo en lowercase por para garantizar que siempre se evaluen todos los nombres sin importar si vienen en mayusculas o minusculas, vicio realmente de crear otros buscadores en el pasado.

Finalmente hago el dispatch de setSearchedValue desde el mismo componente de Searcher en donde tengo almacenada la l贸gica correspondiente a este input, al igual que agregu茅 un label para mostrar el valor buscado, aunque realmente lo hice fue para probar que me estuviere conservando el estado de forma correcta.

import { Input } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import { setSearchedValue } from '../slices/dataSlice';

const Searcher = () => {

    const searchedValue = useSelector( (state) => state.data.searchedValue );

    const dispatch = useDispatch();

    const handleChangeSearchedValue = (input) => {
        dispatch( setSearchedValue(input.target.value) );
    }

    return ( 
        <Input.Group className='searchBoxGroup'>
            <Input.Search placeholder="Buscar" value={searchedValue} onChange={handleChangeSearchedValue} />
                <label>
                    {searchedValue !== "" ? ("Searching (" + searchedValue + ")") : null}
                </label>
        </Input.Group>
    )
}

export default Searcher;

De tener en cuenta que en este aso dado que estamos usando AntDesign el evento en el Input Search para capturar correctamente el valor es onChange el cual se encuentra en la documentaci贸n.

Esa fue mi soluci贸n

Muy bueno el curso, lo que m谩s me gust贸 es el uso de redux para proyectos legacy y luego redux-toolkit como alternativa super f谩cil. Esto fue como la evoluci贸n de vuex a pinia en Vue.js

Muy buen curso, me gusto el enfoque, se aprendieron muchas cosas. para dejar mi aporte nomas para evitar que se guarde tanta data en el state

Puse esto en API para solamente mandar al state las propiedades que utilizamos

export const getPokemonDetail = (pokemon) => {
    return axios
        .get(pokemon.url)
        .then(res => {
            const data = res.data
            return {
                id: data.id,
                name: data.name,
                sprites: data.sprites,
                types: data.types,
                // favorite: false,
            }
        })
        .catch(err => console.log(err))
}

Por otro lado muy de acuerdo con todos los comentarios que necesitamso un curso a profundidad de Redux Toolkit

Muchas gracias profesora por este curso tan bien explicado.
Entre al curso con ideas generales y ahora me voy teniendo toda la base clara y solida adem谩s de poder adaptarme a las diferentes herramientas relacionadas con Redux, aunque claro yo propondre toolkit sin dudarlo jajaja. Hasta un proximo curso, espero encontrarla en otro curso de React.

Buen cursoo Gracias profa.

El buscador de pokemons que hemos incluido en la UI, sin duda lo m谩s 煤til de este curso.

Excelente!! Gracias por todo lo aprendido.

Muy buen curso! Gracias!

Me gust贸 mucho el curso y pude practicar lo aprendido.

Mi soluci贸n al buscador.

Agregue dos variables al estado del dataSlide para poder realizar la b煤squeda y que al agregar a favoritos pudiera guardar la informaci贸n, luche mucho en encontrar una soluci贸n para o de favoritos pero al final esto fue lo mejor que se me ocurri贸.

Aqui dejo el link de la solucion

Y del despliegue en gh-pages

Super este curso, al entender el flujo de redux desde lo b谩sico y luego pasar a reduxjs/toolkit pude entender a fondo c贸mo es que estaban manejando en el proyecto en el que me encuentro ya si poder implementar mis nuevos funcionalidades que necesitaba sin perderme.

Nice course Mariangelica. I struggled in certain parts, but overall, it was neat.

Habia hecho la solucion sin usar Redux y haciendo el filter localmente con useState, pero como es un curso de Redux lo cambie agregando un property 鈥渟earchText鈥 en el slice ui

const initialState = {
  loading: false,
  searchText: '',
};

Me parecio la mejor solucion sin tener que agregar otroa copia de todos los pokemons en un property 鈥渇iltered鈥 o algo similar.

En mi componente App.js hago el filtrado usando los valores del state y uso 鈥渦seMemo鈥 para que la operacion de filtrado no se haga en cada renderizado y solo se haga cuando cambie el valor del texto a filtrar o la lista total de pokemons:

 const { loading, searchText } = useSelector(
    (state) => state.ui,
    shallowEqual
  );

  const filteredPokemons = useMemo(() => {
    const lowerCaseSearchText = searchText.toLowerCase();
    return pokemons.filter(({ name }) =>
      name.toLowerCase().includes(lowerCaseSearchText)
    );
  }, [searchText, pokemons]);

Y por ultimo en mi componente Searcher solo agregue mi funcion 鈥渉andleOnSearch鈥 y la pase al componente en la propiedad 鈥渙nSearch鈥 en lugar de 鈥渙nChange鈥 para que la busqueda se haga la presionar enter o al hacer click en el boton de busqueda, y no mientras se escribe:

import { Input } from 'antd';
import { useDispatch } from 'react-redux';
import { setSearchText } from '../slices/uiSlice';

const Searcher = () => {
  const dispatch = useDispatch();
  const handleOnSearch = (searchText) => dispatch(setSearchText(searchText));
  return <Input.Search placeholder='Buscar...' onSearch={handleOnSearch} />;
};

export default Searcher;

un curso que me costo un poco de trabajo entender algunos conceptos, pero sin duda necesario para mis actividades diarias en tech

Gracias @musarte.dev!

que buen curso

Me encant贸 el curso, pero tengo que estudiarlo m谩s, a煤n me cuesta bastante 馃槄

隆Excelente curso! Pondr茅 en pr谩ctica lo aprendido en mi siguiente proyecto con React. 隆Muchas felicidades a la maestra!

猸怣e enfoque en una soluci贸n de mejorar el UX del usuario guardando los favoritos en otra vista con React Router ya que al navegar entre varias paginas se pierden los pokemones.

https://pokedux-one.vercel.app/
https://github.com/Diegosotomayor1/pokedux

Hola, les comparto mi soluci贸n
https://alexguaman.github.io/pokedux/

Mi soluci贸n al reto utilizando vite + typeScript:
(Cualquier duda o aporte de ts lo dejan en los comentarios)

// dataSlice.tsx
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import { getPokemonDetails, getPokemons, PokemonDetailType, PokemonType } from "../api";
import { setLoading } from "./uiSlice";

type InitialState = {
    pokemons: PokemonDetailType[],
    searched : string
}

const initialState: InitialState = {
    pokemons: [],
    searched: ''
}

export const fetchPokemonsWithDetails = createAsyncThunk(
    'data/fetchPokemonsWithDetails',
    async (_,{dispatch}) =>{
        try {
            dispatch(setLoading(true))
            const pkmns = await getPokemons();
            const pkmnsDetailed = await Promise.all((pkmns as PokemonType[]).map(pkmn=>getPokemonDetails(pkmn)))
            dispatch(setPokemons(pkmnsDetailed))
            dispatch(setLoading(false))
        } catch (err) {
            console.error(err)
            dispatch(setLoading(false))
        }

    }
)

export const dataSlice = createSlice({
    name: 'data',
    initialState,
    reducers:{
        setPokemons: (state,action:PayloadAction<PokemonDetailType[]>)=>{
            state.pokemons = action.payload
        },
        setFavorite: (state, action:PayloadAction<{pokemonId:number}>)=>{
            const currentPokemonIndex = state.pokemons.findIndex(
                (pkmn:PokemonDetailType) => {
                    return pkmn.id  === action.payload.pokemonId
                }
            )
            if(currentPokemonIndex >= 0){
                const isFavorite = state.pokemons[currentPokemonIndex].favorite
                state.pokemons[currentPokemonIndex].favorite = !isFavorite
            }
        },
        setSearched: (state, action:PayloadAction<string>)=>{
            state.searched = action.payload
        }
    }
})

export const dataReducer = dataSlice.reducer;

export const { setPokemons, setFavorite, setSearched } = dataSlice.actions;
// App.tsx
import { useEffect } from 'react'
import { Search } from './components/Search'
import { PokemonList } from './components/PokemonList'
import { Col, Spin } from 'antd'
import { useAppDispatch, useAppSelector } from './slices'
import { fetchPokemonsWithDetails } from './slices/dataSlice'
import logo from './statics/logo.svg'
import './App.css'

function App() {
  const pokemons = useAppSelector((state)=>state.data.pokemons)
  const searched = useAppSelector(state=>state.data.searched)
  const pokemonsSearched = pokemons.filter(pokemon=>pokemon.name.toLowerCase().includes(searched.toLowerCase()))
  const loading = useAppSelector((state)=>state.ui.loading)
  const dispatch = useAppDispatch();
  useEffect(()=>{
    dispatch(fetchPokemonsWithDetails())
  },[])
  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={pokemonsSearched} />
      }
    </div>
  )
}

export default App;

// Search.tsx
import { Input } from "antd";
import { useAppDispatch, useAppSelector } from "../slices";
import { setSearched } from "../slices/dataSlice";

const Search = ()=>{
    const searched = useAppSelector(state=>state.data.searched)
    const dispatch = useAppDispatch();
    const handler = (event:any)=>{
        console.log(event.target.value)
        dispatch(setSearched(event.target.value))
    }
    return <Input.Search value={searched} onChange={handler} placeholder="Buscar..." style={{marginBottom: 10}}/>
}

export {
    Search
}

para el buscador, a帽ad铆 una nueva propiedad search al estado

const initialState = {
    pokemons: [],
    search: []
}

luego a帽ad铆 una nueva acci贸n

export const dataSlice = createSlice({
    name: 'data',
    initialState,
    reducers: {
        setPokemons: (state,action) => {
            ....
        setSearch: (state, action) => {
            state.search = state.pokemons.filter(poke => poke.name.includes(action.payload))
        }
    }
})

desde el componente App con el hook useSelector, obtuve el valor de esa nueva propiedad

const search = useSelector(state => state.data.search)

y cree un estado local con valor por defecto de false

const [isSearch,setIsSearch] = useState(false)

todo esto lo hice para poder pasar props de una manera din谩mica dependiendo el valor del estado local a el componente PokemonList desde el componente App

<PokemonList pokemons={isSearch ? search : pokemons} />

la funci贸n para cambiar el valor del estado la pase por prop a el componente Searcher

<Searcher  setIsSearch={setIsSearch} />

quedando de esta forma App.js:

const App = () => {
  const dispatch = useDispatch()
  const pokemons = useSelector(state => state.data.pokemons, shallowEqual)
  const search = useSelector(state => state.data.search)
  const loading = useSelector(state => state.ui.loading)

  const [isSearch,setIsSearch] = useState(false)
  
  useEffect(()=> {
    dispatch(fetchPokemonsWithDetails())
  }, [])

  return (
    <div className="App">
      <Col span={4} offset={10}>
        <img src={pokedux} alt='pokedux' />
      </Col>
      <Col span={8} offset={8}>
        <Searcher  setIsSearch={setIsSearch} />
      </Col>
      {
        loading ? 
        <Col offset={12}>
          <Spin spinning size='large'  />
        </Col> :
        <PokemonList pokemons={isSearch ? search : pokemons} />
      }
      
    </div>
  );
}

export default App

desde el componente Searcher solo recib铆 la prop y cree la l贸gica a帽adiendo eventos para poder invocar la funci贸n que esperamos en la prop y su valor cambie a true cuando se detecte un cambio en el input, y disparar la acci贸n que anteriormente hab铆amos creado pas谩ndole como par谩metro el valor que recibimos en el input

const Searcher = ({ setIsSearch })=> {
    const dispatch = useDispatch()

    const handleChange = (e)=> {
        if (e.target.value === ''){
            setIsSearch(false)
        }

        dispatch(setSearch(e.target.value))
        setIsSearch(true)
    }

    return <Input.Search placeholder="Buscar.." style={{marginBottom: 10}} onChange={handleChange}  />
}

Comparto el resultado final del proyecto del curso con el buscador: Proyecto pokedux