No tienes acceso a esta clase

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

Conclusiones

22/22
Recursos

¿Cómo implementar la funcionalidad de búsqueda en una aplicación?

Has llegado a un momento crucial en tu aprendizaje sobre ReWiz: aplicar todo lo aprendido para agregar funcionalidad a una caja de búsqueda en tu aplicación. El reto consiste en permitir que el usuario ingrese un texto y que la lista de Pokémones se filtre según coincidencias de nombres. Asegúrate de compartir tu solución con la comunidad en la caja de aportes para fomentar el aprendizaje colectivo. Ahora, veamos los conceptos clave que te ayudarán en esta tarea.

¿Qué es ReWiz y cuál es su ciclo de vida?

ReWiz es una librería que ofrece un enfoque innovador para gestionar estados en aplicaciones React. Entender su ciclo de vida es esencial para implementar la funcionalidad de búsqueda de manera eficaz. A través del curso, has aprendido a usar esta herramienta de manera efectiva, comparándola incluso con Context API para tener una perspectiva más amplia sobre sus ventajas y limitaciones.

¿Qué son los Miralwares y cómo crearlos?

Los Miralwares son componentes clave en ReWiz que permiten manipular acciones o procesos antes de que lleguen al reducer. Has tenido la oportunidad de crear tu propio Miralware, lo que te da la habilidad de interceptar y modificar acciones en la aplicación, haciéndolos perfectos para tareas como la autenticación o el registro.

¿Cómo se utiliza Redux Tone?

Redux Tone es una herramienta que complementa ReWiz al ampliar las capacidades de middleware. Te ofrece mayor flexibilidad al manejar procesos asíncronos. Implementar Redux Tone en tu proyecto te puede ayudar a estructurar complejas cadenas de acciones, muy útil en aplicaciones a gran escala.

El papel de la inmutabilidad y Redux Toolkit

La inmutabilidad es un principio fundamental al manejar el estado con Redux. Aprender a mantener los estados inmutables te asegura que las actualizaciones se realicen de forma controlada y predecible. Además, con Redux Toolkit, te has familiarizado con los Slices, que simplifican la definición de reducers y actions, reduciendo significativamente la cantidad de código necesario.

¿Existen alternativas a Redux?

Aunque el curso se ha centrado en Redux, debes saber que hay otras herramientas emergentes que pueden ser más adecuadas para ciertos proyectos. Sin embargo, Redux sigue siendo ampliamente utilizado en el ámbito laboral debido a su estabilidad y la familiaridad de muchos desarrolladores con su ecosistema.

Motivación para seguir aprendiendo

Es importante no detenerse aquí. Te animamos a tomar el examen final para consolidar tus conocimientos y dejar una reseña si el curso fue de tu agrado. Además, continúa explorando este emocionante mundo tecnológico y comparte tus experiencias e ideas en las redes sociales. Esto no solo te ayudará a crecer profesionalmente, sino también a conectarte con otros entusiastas de la tecnología.

Aportes 48

Preguntas 3

Ordenar por:

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

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 “the old way”, he aplicado redux toolkit en trabajo pero nunca la manera anterior a este. Muchas gracias por el curso!

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

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 😄

Me fascino este curso! siempre le temo a React sin embargo ya estoy pronto de acabar la ruta con pasos firmes en mi aprendizaje.

¡Qué bien me encantó este curso!!

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 😄

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/

entendí como funciona Redux pero no se como implementarlo en mis proyectos de trabajo FreeLancer

Buen cursoo Gracias profa.

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

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

deberian tener un curso de Context reducer del propio react ya que es una manera mas rencilla de la misma funcionalidad de redux <https://es.react.dev/learn/scaling-up-with-reducer-and-context>
# Rick and Morty API Les comparto mi práctica final para reforzar lo aprendido. <https://rickandmorty-api-react.web.app/> Está construída con React, React Redux Toolkit y React Router. El curso no está tan bueno, pero les adelanto que pueden lograrlo si lo complementan con documentación y videos en youtube. Espero les guste!

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

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

Excelente curso! Comparto mi versión de la app con algunas mejoras que implementé: * Agregué la funcionalidad del buscador para filtrar Pokémon. * Incorporé un paginador que muestra los Pokémon de a 8 por página. * Implementé la persistencia de favoritos usando `localStorage`, lo que permite mantener los datos al recargar la página. **Link al deploy:** <https://ayeleniasich.github.io/pokedux/> **Repositorio en GitHub:** <https://github.com/AyelenIasich/pokedux> ![](https://static.platzi.com/media/user_upload/preview_pokedux-ed58d4bb-3793-4061-908e-db395f3c6baa.jpg)
qué hermoso es trabajar con redux toolkit ♥ a practicar!
Profe muchas gracias, increíble curso. Este tema que puede ser muy enredado, lo explicaste de forma magistral. Muchas gracias profe y platzi.

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.

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 “searchText” 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 “filtered” o algo similar.

En mi componente App.js hago el filtrado usando los valores del state y uso “useMemo” 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 “handleOnSearch” y la pase al componente en la propiedad “onSearch” en lugar de “onChange” 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!

⭐Me 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