隆Bienvenida! Este es un curso especial de React Hooks

1

驴Qu茅 aprender谩s en el Curso Profesional de React Hooks?

2

驴Qu茅 son los React Hooks y c贸mo cambian el desarrollo con React?

Introducci贸n a React Hooks

3

useState: estado en componentes creados como funciones

4

useEffect: olvida el ciclo de vida, ahora piensa en efectos

5

useContext: la fusi贸n de React Hooks y React Context

6

useReducer: como useState, pero m谩s escalable

7

驴Qu茅 es memoization? Programaci贸n funcional en JavaScript

8

useMemo: evita c谩lculos innecesarios en componentes

9

useRef: manejo profesional de inputs y formularios

10

useCallback: evita c谩lculos innecesarios en funciones

11

Optimizaci贸n de componentes en React con React.memo

12

Custom hooks: abstracci贸n en la l贸gica de tus componentes

13

Third Party Custom Hooks de Redux y React Router

Configura un entorno de desarrollo profesional

14

Proyecto: an谩lisis y retos de Platzi Conf Store

15

Instalaci贸n de Webpack y Babel: presets, plugins y loaders

16

Configuraci贸n de Webpack 5 y webpack-dev-server

17

Configuraci贸n de Webpack 5 con loaders y estilos

18

Loaders de Webpack para Preprocesadores CSS

19

Flujo de desarrollo seguro y consistente con ESLint y Prettier

20

Git Hooks con Husky

Estructura y creaci贸n de componentes para Platzi Conf Store

21

Arquitectura de vistas y componentes con React Router DOM

22

Maquetaci贸n y estilos del home

23

Maquetaci贸n y estilos de la lista de productos

24

Maquetaci贸n y estilos del formulario de checkout

25

Maquetaci贸n y estilos de la informaci贸n del usuario

26

Maquetaci贸n y estilos del flujo de pago

27

Integraci贸n de 铆conos y conexi贸n con React Router

Integraci贸n de React Hooks en Platzi Conf Merch

28

Creando nuestro primer custom hook

29

Implementando useContext en Platzi Conf Merch

30

useContext en la p谩gina de checkout

31

useRef en la p谩gina de checkout

32

Integrando third party custom hooks en Platzi Conf Merch

Configura mapas y pagos con PayPal y Google Maps

33

Paso a paso para conectar tu aplicaci贸n con la API de PayPal

34

Integraci贸n de pagos con la API de PayPal

35

Completando la integraci贸n de pagos con la API de PayPal

36

Paso a paso para conectar tu aplicaci贸n con la API de Google Maps

37

Integraci贸n de Google Maps en el mapa de checkout

38

Creando un Custom Hook para Google Maps

Estrategias de deployment profesional

39

Continuous integration y continuous delivery con GitHub Actions

40

Compra del dominio y despliega con Cloudflare

Optimizaci贸n de aplicaciones web con React

41

Integraci贸n de React Helmet para mejorar el SEO con meta etiquetas

42

An谩lisis de performance con Google Lighthouse

43

Convierte tu aplicaci贸n de React en PWA

Bonus: trabaja con Strapi CMS para crear tu propia API

44

Crea una API con Strapi CMS y cons煤mela con React.js

驴Qu茅 sigue en tu carrera profesional?

45

Pr贸ximos pasos para especializarte en frontend

useReducer: como useState, pero m谩s escalable

6/45
Recursos

Aportes 61

Preguntas 18

Ordenar por:

Los aportes, preguntas y respuestas son vitales para aprender en comunidad. Reg铆strate o inicia sesi贸n para participar.

馃鈿涳笍 useReducer no es una alternativa a Redux, solo 鈥渢oma prestado鈥 el uso de reducers para actualizar nuestro estado local.

De esta clase, esperaba que nos hablaran un poco sobre los casos de uso de useReducer, ventajas y desventajas diferencias entre useState y useReducer, cuando usar uno y cuando usar el otro. Implementar el useReducer porque se puede hacer no me explica todo lo que esperaba de esta clase.

Apuntes

  • Como useState, pero m谩s escalable
  • Implementa una forma m谩s amigables y llena de caracteristicas para trabajar con el estado
  • useReducer a menudo es preferible a useState cuando:
    • se tiene una l贸gica compleja que involucra m煤ltiples subvalores
    • el pr贸ximo estado depende del anterior.

Ejemplo de useReducer

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

Ideas/conceptos claves

useReducer.- Esta basada en la idea de Redux de tener una funci贸n pura que devuelve estados pero que se puede aplicar de una manera local en componentes

Recursos

Referencia de la API de los Hooks - React

As铆 va la app

Los quiero bye

/* Super complicado pero lo vas a usar muchisimas veces
1. agrega el useReducer
2. crea un estado inicial: la lista de favoritos vacia
3. crea el reducer, es una funcion que usa switch identificar el metodo a usar
	  recive state y action:
		state: el estado actual.
		action: objeto con el metodo que quieres ejecutar junto con el contenido.
		action.type: el metodo a ejecutar.
		action.payload: el contenido nuevo que quieres manejar.
		Ej: ADD_TO_FAVORITE toma el estado actual y le agrega el contenido de payload
4. crea el use reducer:
		favorite: es el nombre el valor de lectura.
		dispatch: el nombre de la funcion para llamar a los metodos.
		useReducer toma dos datos, el primero es el reducer, contenedor del switch de metodos
		el segundo parametro es el estado inicial, que por lo regular es vacio
5. Metodo que llama al dispatch / la funcion para acceder a un metodo del reducer
		contiene el type que es el nombre del metodo
		y el payload que es el contenido que se manejara al correr el metodo
		el contenido del payload en este caso se obtiene del tag que lo llamo con el onclick
6. Onclick que manda a llamar al dispatch
		es un onclick que al ejecutarse manda la data del caracter
		en la funcion esta info sera mandada al reducer y de ahi al state final en favorite
7. map al contenido de favorite, listado del contenido de este array, 
		si no hay pues no se ve nada
*/
import React, { useState, useEffect, useReducer } from 'react'; // 1

const initialState = { // 2
  favorites: [],
}
  
const favoriteReducer = (state, action) => { // 3
  switch(action.type) {
    case 'ADD_TO_FAVORITE':
      return {
        ...state,
        favorites: [...state.favorites, action.payload]
      };
      default: return state;
  }
}

const Characters = () => {
  const [characters, setCharacters] = useState([]);
  const [favorite, dispatch] = useReducer(favoriteReducer, initialState); // 4

  useEffect(() => {
    fetch('https://rickandmortyapi.com/api/character/')
    .then(response => response.json())
    .then(data => setCharacters(data.results))
  }, [])

  const handleClick = favorite => {
    dispatch({ type: 'ADD_TO_FAVORITE', payload: favorite })
  }
  
  return (
    <div className='characters'>

      {favorite.favorites.map((favorite) => ( //7
        <li class='item' key={favorite.id}>
          {favorite.name}
        </li>
      ))}
      {characters.map((character) => (
        <div class='item' key={character.id}>
          <h2>{character.name}</h2>
          <button type='button' onClick={() => handleClick(character)}>add</button> //6
        </div>
      ))}
    </div>
  );
}

export default Characters;

UFFF鈥 carece de pedagog铆a este curso. a mi modo de ver est脿 muy mal estructurado.

Una cosa es dominar un tema y otra bien diferente saber ense帽arlo鈥

Agregue un boton para eliminar de favoritos:

Aqui les dejo mi codigo:

Este es el componente de Characters:

import { useState, useEffect, useReducer } from 'react';
import Card from './Card';
import '../styles/Characters.css'

const initialState = {
    favorites: []
}

const favoriteReducer = (state, action) => {
    switch (action.type) {
        case 'ADD_TO_FAVORITE':
            const isExist = state.favorites.find(item => item.id === action.payload.id)
            if (isExist) return { ...state }
            return {
                ...state,
                favorites: [...state.favorites, action.payload]
            }

        case 'REMOVE_FAVORITE':
            return {
                ...state,
                favorites: state.favorites.filter(items => items.id !== action.payload)
            };

        default:
            return state;
    }
}

const Characters = () => {
    const [characters, setCharacters] = useState([]);
    const [favorites, dispatch] = useReducer(favoriteReducer, initialState);

    useEffect(() => {
        fetch('https://rickandmortyapi.com/api/character/')
            .then(response => response.json())
            .then(data => setCharacters(data.results))
    }, []);

    const handleClick = (favorite) => {
        dispatch({ type: 'ADD_TO_FAVORITE', payload: favorite })
    }

    const handleClickRemove = (id) => {
        dispatch({ type: 'REMOVE_FAVORITE', payload: id })
    }

    return (
        <div className="container">

            {favorites.favorites.length > 0 &&
                <div>
                    <h2>Favoritos</h2>
                    <ul className="row Characters">
                        {favorites.favorites.map(favorite =>
                            <li className="col-6 col-md-3" key={favorite.id}>
                                <Card {...favorite} isFavorite={true} handleClickRemove={handleClickRemove} />
                            </li>
                        )}
                    </ul>
                </div>
            }


            <h2>Personajes</h2>
            <ul className="row Characters">
                {characters.map(character =>
                    <li className="col-6 col-md-3" key={character.id}>
                        <Card {...character} handleClick={handleClick} />
                    </li>
                )}

            </ul>
        </div>
    )
}

export default Characters;

Y este es mi componente Card:

import '../styles/Card.css';


const Card = (props) => {

    const { id, name, status, species, gender, image, isFavorite, handleClick, handleClickRemove } = props;

    return (
        <div className="Card">
            <img src={image} alt={name} />
            <h5>Name: {name}</h5>
            <h5>Status: {status}</h5>
            <h5>Species: {species}</h5>
            <h5>Gender: {gender}</h5>
            {!isFavorite ?
                <button
                    type="button"
                    className='btn btn-primary'
                    onClick={() => handleClick({ id, name, status, species, gender, image })}
                >
                    Agregar a favoritos
                </button>
                :
                <button
                    type="button"
                    className='btn btn-danger'
                    onClick={() => handleClickRemove(id)}
                >
                    Eliminar de favoritos
                </button>
            }
        </div>
    )
}

export default Card

Aqui mi funcionalidad de favoritos:

Para el que le interese la l贸gica del reducer:

const initialState = {
	favorites: [],
};

const favoriteReducer = (state, action) => {
	switch (action.type) {
		case 'ADD_TO_FAVORITES':
			return {
				...state,
				favorites: [...state.favorites, action.payload],
			};
		case 'REMOVE_FROM_FAVORITES':
			return {
				...state,
				favorites: [
					...state.favorites.filter((favorite) => favorite !== action.payload),
				],
			};
		default:
			return state;
	}
};

const Characters = () => {
	const [characters, setCharacters] = useState([]);
	const [favorites, dispatchFavorites] = useReducer(
		favoriteReducer,
		initialState
	);

	useEffect(() => {
		fetch('https://rickandmortyapi.com/api/character/')
			.then((response) => response.json())
			.then((data) => setCharacters(data.results));
	}, []);

	const handleFavorite = (favorite) =>
		dispatchFavorites({
			type: !!findCharacterInFavorites(favorite)
				? 'REMOVE_FROM_FAVORITES'
				: 'ADD_TO_FAVORITES',
			payload: favorite,
		});

	const findCharacterInFavorites = (favorite) =>
		favorites.favorites.find((character) => character.id === favorite.id);

	console.log(favorites);
	return (
		<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
			{characters.map((character) => (
				<Card
					props={{
						...character,
						handleFavorite: () => handleFavorite(character),
						favorite: !!findCharacterInFavorites(character),
					}}
				/>
			))}
		</div>
	);
};

No fue muy clara la clase, el profesor asume que conocemos la l贸gica de Redux y omite explicaciones de ciertas partes, me toc贸 buscar informaci贸n externa para entender lo que 茅l asume que sabemos.

Asi esta quedando la APP, utilizando Tailwind CSS.

Realmente haber aprendido Redux anteriormente, me ayud贸 mucho a comprender esta clase!! Estructure el proyecto de una manera distinta, creando una carpeta **鈥楥haracter鈥 ** dentro de context, y esa carpeta contiene 鈥楥haracterContext.js鈥 que solamente contiene el createContext de Characters, luego cre茅 CharacterReducer, que se encarga de manejar la l贸gica de los dispatchs, y por 煤ltimo, CharacterState, la que contiene el initialState y retorna el context de character con su provider y value, ademas agregue un custom hook con useContext, para no tener que importar siempre el contexto y el useContext, sino que solamente importo el custom hook donde necesite hacer uso del contexto.
Me esta quedando de la siguiente manera:

As铆 es como esta estructurado cada archivo dentro de la carpeta Character de context, luego solamente necesite encapsular el componente characters dentro de CharacterState, de la siguiente manera

Y luego dentro de Characters.jsx, ya tengo acceso al estado, atributos y funciones, pasadas en el provider del context. Utilizando el custom hook que cre茅.

Y as铆 se ve gr谩ficamente 馃槃

Siento que el las explicaciones estan siendo muy superficiales鈥 No todo se aprende a puro ejemplo

Quer铆a validar que no se pudiera seleccionar el mismo item para el listado de favoritos asi que realic茅 esta condici贸n en el reducer por si alguien le interesa.

const favoriteReducer = (state, action) =>{
  switch(action.type){
    case 'ADD_TO_FAVORITE':
      const validation = state.favorites.filter((favorite)=> action.payload.id === favorite.id)

      if(validation.length === 0){
        return {
          ...state,
          favorites:[...state.favorites, action.payload]
        }
      }
      else{
        return {...state}
      }
    default:
      return state
  }
}

Hola, tengo una duda o pregunta鈥

const initialState = {
  favorites: [],
};
const favoriteReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TO_FAVORITE':
      return {
        ...state,
        favorites: [...state.favorites, action.payload],
      };

    default:
      return state;
  }
};

el c贸digo anterior no deber铆a ir en un archivo aparte?

Les comparto como va quedando mi plataforma.

B谩sicamente, el useReducer es un eventHandler que, al ser llamado, realiza la acci贸n que nosotros le decimos que haga; en esta clase fue a帽adir un elemento m谩s al initialState que comienza vac铆o, y que cada que le damos click, llama al 鈥渆ventHandler鈥 (reducer), le ind铆ca que la acci贸n ser谩 ADD_TO_FAVORITE.
El hook useState es simplemente el estado cl谩sico de un componente.

Realmente, si no entendiste lo suficiente esta clase no es algo de lo que te debes de preocupar demasiado, ya que Redux y useReducer comprenden varios conceptos de programaci贸n funcional inmutable y pura. Junto a los conceptos de reducer. actions, store, etc., y es mejor comenzar entendiendo eso primero.

Levante la mano a quien le pareci贸 m谩s sencillo el useContext 鉁

Este video me ayud贸 a entender un poco m谩s.
Al parecer useState es para cosas m谩s simples y useReducer es para cosas m谩s complejas

Muy interesante la clase realmente ! si quieren profundizar un poco mas en este Hook aqui les dejo el link a la documentaci贸n.

Les dejo un aporte:
-Evitar agregar dos personajes iguales

  const handleClick_AddFavourite = favourite =>{
    if (!favourites.favourites.includes(favourite)){
      dispatch( { type:'ADD_TO_FAVOURITE', payload : favourite})
    }else{
      alert('Ya has agregado este Personaje a tus Favoritos.')
    }

  };

useReducer: como useState, pero m谩s escalable


useReducer implementa una nueva forma m谩s amigable de usar el estado. Si haz trabajado con Redux te vas a familiarizar con el contexto ya que tiene:

  • Estado inicial
  • Reducer
  • Dispatch (para enviar la informaci贸n)

no lo se me parece mejor usar redux

Dejo un tutorial a lo visto en est谩 clase:

Pasos para Instanciar un nuevo useReducer

https://www.linkedin.com/public-profile/settings?trk=d_flagship3_profile_self_view_public_profile

En los anteriores apartados hemos examinado la manera en la que iniciamos el estado inicial de nuestro reducer y la funci贸n que se encargar谩 de establecer el cambio del estado de acuerdo al estado con el que ejecutemos la funci贸n reductora.

Ahora es momento de establecer la manera en la que llamaremos esta funci贸n reductora y como le indicamos que parte de nuestro estado se afecta.

  1. Importar el useReduce de la libreria de React
import { useReducer } from 'react'

Los pasos 2 y 3 los podemos definir por fuera del componente o en un archivo.js externo debido a que son l贸gica del programa.

  1. Definir el valor inicial de nuestro Reducer . Idealmente se usa para manejar m煤ltiples estado a la vez, por lo tanto, lo encontraras como un objeto que contiene todos los estados que se requieren manejar. En este ejemplo, manejaremos solo una variable en su interior.
//Estado inicial del reducer
const initialState = {
  favorites:[],
}
  1. Debemos instanciar una funci贸n que se encargue de modificar el estado de nuestras variables.
const reducer = (state, action)=>{
  switch(action.type){
    case 'ADD_TO_FAVORITE':
      return{
        ...state, 
        favorites:[...state.favorites, action.payload]
      }
      default:
        return state;
  }
}
  • La funci贸n requiere un state y un action
  • El state sera el objeto con las variables que nosotros definamos como parte del objeto. En este caso solo tiene una variable.
  • La acci贸n servira como la bandera que ejecute una u otra acci贸n. En este caso, tenemos dos posibles acciones 鈥楢DD_TO_FAVORITE鈥 o un estado por defecto
  • El switch evalua el tipo de acci贸n que mandado ha llamar la funci贸n reducer
  • Cada case debe retornar como primer elemento el anterior estado 鈥tate, y como segundo valor las variables que se modificaran en su llamado. En este ejemplo, solo se requiere modificar una sola variable, pero el estado puede contener m煤ltiples variables.
  • Si tenemos una variable que tomara valores de manera din谩mica este debera ser definido como action.payload
  • Las acciones din谩micas son cambios por una acci贸n en el teclado (llenar un formulario), llamados a una api. En este ejemplo consumimos los valores del array seg煤n los datos de una api. Es por ello que se define un payload
  • Siempre debemos definir un caso por defecto en caso de que se llame la funci贸n reducer y no coincida con ninguna de las posibles acciones. Lo m谩s aconsejable es regresar el mismo objeto de state sin ning煤n cambio retun state.

Los siguientes pasos requieren se ejecuten dentro del componente

  1. Dentro de nuestro componente debemos llamar instanciar el reducer
const [state, dispatch] = useReducer(reducer, initialState);
  • El state tomara como valor inicial el objeto que se definia dentro de initialState. Tambien podemos escribir directamente un objeto { favorites:[] } pero no es recomendable para mantener el orden en el c贸digo.
  • Por convensi贸n se define como nombre de la funci贸n reductora dispatch. Cada vez que necesitemos modificar un estado mediante el reducer la llamaremos dispatch. Tomalo como un cambio del nombre de tu funci贸n reduce. El nombre dispatch no es obligatorio puedes llamarla nuevamente reducer u otro nombre que para tu l贸gica sea mas diciente.
  1. Si he explicado bien entiendes que cada variable dentro de tu state cambiara seg煤n una acci贸n o evento diferente dentro de t煤 App. Cada una de estos eventos debe llamar una funci贸n que llame el reduce y le entregue los param茅tros necesarios para que pueda interpretar que acci贸n se produjo. Recuerda, en este caso solo tenemos una acci贸n 鈥楢DD_TO_FAVORITE鈥 por lo tanto solo requerimos una funci贸n. Pero podemos tener muchas funciones m谩s. La de este ejemplo se llama cuando damos click sobre el bot贸n de agregar.
const hadleClick = character =>{
    dispatch({
      type: 'ADD_TO_FAVORITE',
      payload:character
    })
  }
  • Cuando damos click se ejecuta la funci贸n handleClick
  • Esta funci贸n tiene como parametro el objeto donde dimos click. En el ejemplo se lo llamo favorite, pero para mi es mas facil de entender si le paso todo el objeto character, puedes pasar solo el nombre u otra informaci贸n que se adapte a tus necesidades.
  • Dentro de esta funci贸n llamaremos la funci贸n encargada de modificar el estado, es similar a llamar el setState pero por convenci贸n la llamamos dispatch
  • Ella debe recibir el type o tipo de acci贸n que debe ejecutar y como en este caso la acci贸n depende de los datos de una Api y es dinamico debemos enviar los datos como un payload.

En este punto ya tienes enlazada toda tu App mediante reducer. Ahora debes agregar un bot贸n que llame a la funci贸n handleClick. Yo cambie un poco la manera en la que se hace el ejemplo en clases para crear cards con los personajes favoritos. Pero puedes adaptar la logica a tus propositos.

Dejo los c贸digos necesarios para mi ejemplo:

  1. Componente characters
mport React from 'react'
import { Character } from './Character';
import { useState, useEffect, useReducer } from 'react'

//Estado inicial del reducer
const initialState = {
  favorites:[],
}

//Funcion de manipulaci贸n del estado del reducer
const favoritesReducer = (state, action)=>{
  //Se valida que tipo de acci贸n se ejecutara cuando se llame
  //El action puede tener diferentes valores
  switch(action.type){
    case 'ADD_TO_FAVORITE':
      return{
        ...state, 
        favorites:[...state.favorites, action.payload]
      }
      default:
        return state;
  }
}

function Characters() {
  const [characters, setCharacters] = useState([]);
 
  const [favorites, dispatch] = useReducer(favoritesReducer, initialState);

  const hadleClick = favorite =>{
    dispatch({
      type: 'ADD_TO_FAVORITE',
      payload:favorite
    })
  }
  useEffect(()=>{
    fetch('https://rickandmortyapi.com/api/character/')
        .then(response => response.json())
        .then(data => setCharacters(data.results)) 
  },[]);

  return (

    <div className='characters '>     

      {
        favorites.favorites.length>0?
        (
          <h2>Favorites</h2>
        ):
        (
          <h2>Not Faivorites</h2>
        )
      }

      {
      favorites.favorites.length>0? 
      (
        <div className='characters-container container'>
        {favorites.favorites.map((favorite, id) =>
         
          <Character 
            character = {favorite}
            key={id}
            hadleClick = {hadleClick}
          />)}
        </div>  
      )
      :
      (<h2>Sin favoritos</h2>) 
      
      }
      <h2>Characters</h2>
      <div className='characters-container container'>
      {

        characters.map((character, id) =>{
          return <Character 
            character = {character}
            key={id}
            hadleClick = {hadleClick}
          />
        })
      }
      </div>
    </div>
  )
}
export {Characters}
  1. Componente Character que crea las cards
import React from 'react'

export const Character = ({character, hadleClick}) => {
 
  return (

    <div className='character-card'>

      <figure className='figure-container'>
        <img src={character.image} alt="image character " />
      </figure>
      <div className="character-text">
          <h2>{character.name}</h2>
          <p><span>Location: </span>{character.location.name}</p>
          <p><span>Origin: </span>{character.origin.name}</p>
          <p><span>Status: </span>{character.status}</p>
      </div>        
      <button type='button'
        onClick={()=>hadleClick(character)}
      >
        Agregar a Favorito
      </button>
    </div>
    
  )
}
  1. Estilos
:root{
  --headin-principal: #04adc7;
  --headin-secundary: #abd268;
  --blue-dark: #1b1b41;
  --white:#FFFFFF;
  --black: #000000;
}
.App {
  background-color: #1b1b41;
}
body {
  margin: 0;
  padding: 0;
  background-color: #1b1b41;
}

html {
 box-sizing: border-box;
}

*, *:before, *:after {
 box-sizing: inherit;
}
img{
  max-width: 100%;
}
a{
  text-decoration: none;
}
li{
  list-style: none;
}
h2{
  color: var(--white);
  text-align: center;
}
.container{
  max-width: 968px;
  margin: 0 auto;
}

/* HEADER */
.header{
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 25px;

}
.headingPrincipal{
  text-align: center;
  color: var(--headin-principal);  
  font-size: 2rem;
  padding: 15px;
  margin: 0;
}
.button-container{
  display: flex;
  column-gap: 15px;
}
.button{
  padding: 5px 15px;
  font-size: 1rem;
  border-radius: 25px;  
  font-weight: 600;
}
.buttonHeader{
  text-align: center;
  border: 4px solid var(--headin-secundary);
  color: var(--blue-dark);
}

/*Characters*/

.characters{
  margin: 0px auto;
  padding:0 25px;
  
}
.characters-container{
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 25px;   
  margin: 20px;
}

@media(min-width: 650px){
  .characters-container{
    flex-direction: row;
    flex-wrap: wrap; 

    
  }
}

@media(min-width: 968px){
  .characters-container{
    grid-template-columns: repeat(3, 1fr);  
  }
}
.characters-container h2{
  display: block;
  
}

.character-card{  
  max-width: 320px;
  min-width: 200px;
  width: 280px;
  height: 550px;
  display: flex;
  flex-direction: column;
  align-items: center;
  border:1px solid var(--headin-secundary);
  border-radius: 15px;
  overflow: hidden;
}
.figure-container{
  margin: 0;  
  width: 100%;
  height: 100%;
  height: auto;
  overflow: hidden;
}
.character-text{
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 10px;
}
.character-card h2{
  color: var(--headin-secundary);
  margin: 0;
  padding: 10px;
  text-align: center;
  align-self: center;
}
.character-card p{
  color: var(--white);
  margin: 0;
  padding: 5px;
}
.character-card span{
  color: var(--headin-secundary);
}

las buenas practicas sugiere crear carpetas para separar el reducer, las actions y opcionalmente los types.

Ademas, si has trabajo con Vuex de Vue, useReducer se hace muy facil ya que recuerda a las actions de Vuex

Qu茅 buena clase, super clara!

Hasta ahora me va as铆 (No soy dise帽ador :c, se que se ve mal)

Aqu铆 podr铆an revisar mas a fondo useReducer, implementando best practices:

https://youtu.be/kK_Wqx3RnHk

Me gusta, pero lo siento muy desordenado. Con Redux queda m谩s claro el c贸digo o es mi percepci贸n?

驴Qu茅 es un reducer?
Son una herramienta que nos permite declarar todos los posibles estados de nuestra App para llamarlos de forma declarativa. Necesitan 2 objetos esenciales: los estados compuestos y las acciones.

Los estados compuestos:

Son un objeto donde van a vivir como propiedades todos nuestros estados

Acciones:

Responsables, al ser disparados, de pasar de un estado a otro.
Este objeto tiene 2 propiedades: action type y action payload.

Action type:

Define el nombre clave para encontrar el nuevo estado.

Action payload:

Es opcional e importante con estados din谩micos. Un estado es dinamico cuando depende del llamado de un API, de lo escrito por el usuario en un input, etc. Estos son estados din谩micos y los recibimos con un payload.
Flujo de trabajo:

Definimos distintos tipos de acciones con type y payload.
Enviamos estas acciones a nuestro reducer.
El reducer define los posibles estados por donde pasara nuestra App.

Con action type elegimos cual de esos estados queremos disponer con el cambio o evento del usuario.

Con action payload damos dinamismo a dicho estado. Ser谩 el mismo estado pero le daremos caracter铆sticas especiales

En este Curso: https://platzi.com/clases/react-estado/
Se puede ver a detalle este Hook. Les recomiendo de la clase 12 a la 15. 馃憣

Hasta el momento voy asi, le a帽adi las funcionalidad de no repetir favorito y de eliminarlo.
![](

Para los que no entendieron que es useReducer() les dejo este ejemplo de como se puede hacer lo mismo que con useState():
Este en un custom hook donde empleo useReducer() para simular a useState()

import React, {useReducer} from "react"

function reducer( state, action ) {
  switch( action.type ) {
    case 'toggle': return !state
    default: throw new Error()
  }
}

export function useMyState( initialState ){
  const [ state, dispatch ] = useReducer( reducer, initialState )
  return{ state, dispatch }
}

Este es el componente donde har茅 uso del custom hook anterior. Su funcionamiento es b谩sico, al presionar el bot贸n quiero cambiar el estado para mostrar el saludo o quitarlo.

import React from "react"
import { useMyState } from "../hooks/useMyState"

export function MyComponent(){
  const { state: sayHello, dispatch: setSayHello } = useMyState( false )

  return(
    <div>
      <p>{ sayHello ? "Hello" : "" }</p>
      <button onClick={ () => setSayHello( { type: 'toggle'} ) }>Greeting</button>
    </div>
  )

}

useReducer() maneja el estado como lo hace useState() y retorna un array con el estado y la funci贸n que modifica a ese estado, la cosa est谩 en que a esa funci贸n a la que llamamos dispatch, solo se encarga de entregar un objeto action a la funci贸n reducer() que es la que va a modificar el estado.

onClick={ () => setSayHello( { type: 'toggle'} ) }

useReducer() recibe como par谩metros la funci贸n reducer() y un valor inicial del estado(como mismo haciamos con useState() ):

 const [ state, dispatch ] = useReducer( reducer, initialState )

La funci贸n reducer se declara afuera del componente o hook, separando el manejo del estado de la l贸gica de renderizado del componente, esta es la gran diferencia con useState(). En esencia esta funci贸n no modifica el estado anterior, si no que crea uno nuevo y React compara ambos y si hay un cambio re-renderiza el componente (inmutable)
La l贸gica es la de un switch/case que va a regresar un valor de estado en funci贸n de los valores de las propiedades del objeto action.


Una analog铆a rapida ser铆a:
-el evento o fetch a una api es el que selecciona una carta para enviar
-el objeto action es la carta( pueden haber m谩s sobre el escritorio )
-el dispatch es el mensajero
-la funci贸n reducer es a quien va dirigida la carta y quien la lee ( al modificar el estado demuestra que la ley贸 )

Tremendo, nunca lo us茅 a este hook pero ahora lo voy a empezar a implementar! 馃槃

Se agregan y se elimina favoritos y se valida no m谩s de 12 favoritos.

import React, { useState, useEffect, useContext, useReducer} from 'react'
import ThemeContext from '../context/ThemeContext';

const initialState = {
    favorites: []
}

const  favoriteReducer = (state, action) =>{
    switch (action.type) {
        case 'ADD_TO_FAVORITE':
            const validation = state.favorites.filter((favorite)=> action.payload.id === favorite.id)
            if(validation.length === 0){
                if (state.favorites.length < 12){
                    return{
                        ...state,
                        favorites: [ ...state.favorites, action.payload ],
                    };
                }
            }
            else{
                return {
                    ...state,
                    favorites: [
                        ...state.favorites.filter( favorite => favorite.id !== action.payload.id )
                    ]
                };
            }break
        case 'DELETE_TO_FAVORITE':
            return{
                ...state,
                favorites: [
                    ...state.favorites.filter( favorite => favorite.id !== action.payload.id )
                ]    
                };
        default:
            return state;
    }
}




const Characters = () => {
    const [characters, setCharacters] = useState([]);
    const [favorites, dispatch] = useReducer(favoriteReducer, initialState);

    useEffect(() => {
        fetch("https://rickandmortyapi.com/api/character/")
        .then(response => response.json())
        .then(data => setCharacters(data.results))

    },[]);
    const {darkMode} = useContext(ThemeContext);
 
    const handleClick = favorite => {
        dispatch({ type: 'ADD_TO_FAVORITE', payload: favorite})

    }
    
    const removeFavs = (favorite) => {
        dispatch({ type: "DELETE_TO_FAVORITE", payload: favorite });
    };

    return (
        <div className="container mt-5">
            <div className="row">
                {favorites.favorites.map(favorite =>(
                    <>
                    <div className="col-1 mb-5" key={favorite.id} >
                        <img className="rounded-circle favoriteCircle" src={ favorite.image } alt="" />
                        <button type="button" onClick= {() => removeFavs(favorite)} >Eliminar</button>
                    </div>
                    </>
                ))}
            </div>
            <div className="row">
                {characters.map(character => (
                    <>
                    <div className="col-4 mb-5">
                    <div className={`border p-3 rounded cardLight ${darkMode ? "border-warning": "border-dark"}`} key={character.id}>
                        <img src={ character.image } alt="" />
                        <h4>{ character.name }</h4>
                        <h6>{ character.status } - { character.species }<small> ({ character.gender }) </small></h6>
                        <button type="button" onClick= {() => handleClick(character)} >{handleClick ? "Agregar a favoritos" : "Eliminar"}</button>
                    </div>
                    </div>
                    </>
                ))}
            </div>
        </div>
    )
}

export default Characters



he le铆do varios comentarios y tengo una opini贸n similar. L as explicaciones se quedan cortas para explicar los hooks

useReducer

馃挕 INFO
Funci贸n alternativa a useState que se vincula a un reducer, de Redux, retornando una referencia al state y a la funci贸n dispatch.

.
useReducer se suele preferir por sobre useState cuando el state presenta una l贸gica compleja:

  • Un encadenamiento de estados dependientes uno, secuencialmente, del otro.
  • Inyectar o hidratar un state local al state manejado por Redux.

.

Estructura b谩sica

const [state, dispatch] = useReducer(reducer, initialArg, funcInit);

Su API, expone 3 argumentos donde:

  1. reducer - el cu谩l deber谩 ser del tipo (state, action) => newState
  2. inicitalArg - ya sea proporcionado por el mismo componente o heredado por el Redux
  3. funcInit - funci贸n para delegar el proceso de inicializaci贸n, el comportamiento ser谩 Lazy

.

Redux Tool Kit (RTK)

RTK es una nueva generaci贸n de herramientas proporcionadas por Redux para actualizar su API abstra铆da para las aplicaciones modernas de React.
Para nuestro ejercicio, podemos utilizar la siguiente estructura de c贸digo:

// store/favorites.jsx
import { createSlice } from "@reduxjs/toolkit";

export const initialFavorites = [];

const slice = createSlice({
  name: "favorites",
  initialState: initialFavorites,
  reducers: {
    addFavorites: (state, action) => {
      state.push(action.payload);
    }
  }
});

export const { addFavorites } = slice.actions;
export const favoritesReducer = slice.reducer;

鈿★笍NOTA
Es necesario instalar react-redux y @reduxjs/toolkit

.
De lo anterior podemos decir que nuestro estado no ser谩 global, solamente hidratado desde el estado local del componente.

Una vez creado lo anterior, obtenemos las referencias a nuestro componente:

import React, { useState, useEffect, useReducer } from "react";

import {
  favoritesReducer,
  initialFavorites,
  addFavorites
} from "../store/favorites.jsx";

Utilizarlo como local store:

const [favorites, dispatch] = useReducer(favoritesReducer, initialFavorites);
 
const handleClick = (favorite) => {
    dispatch(addFavorites(favorite));
  };

  return (
      {characters.length > 0 &&
        characters.map((character) => (
          <div key={character.id}>
            <h2>{character.name}</h2>
            <button type="button" onClick={() => handleClick(character)}>
              Agregar a Favoritos
            </button>
          </div>
        ))}
    </div>
  );

Link de c贸digos

Para que no agregue 2 veces el mismo personaje(Character):

const favoriteReducer = (state, action) => {
    switch (action.type) {
        case 'ADD_TO_FAVORITE':
            return {
                ...state,
                favorites: [...state.favorites.filter(item => item.id !== action.payload.id),
                            action.payload],
            };
        default:
            return state;
    }
}

Entonces usamos use Reducer cuando queremos manejar state dentro del componente de una mejor forma, es decir este no es como Redux que con connect se pude traer a los props, este se declara y se usa dentro del componente y sus hijos, no?

Les dejo un aporte de Juan David Castro Gallego en donde explica con bastante claridad como funciona. Tambi茅n abre el espectro mostrando diferentes posibilidades.
No se si ya da alg煤n curso pero creo que ser铆a un excelente profesor.
https://platzi.com/blog/usestate-vs-usereducer/

Diferencias entre useReduce y Redux: https://www.robinwieruch.de/redux-vs-usereducer
En pocas palabras con useReducer estamos limitados a emitir actions solo a un reducer, adem谩s de que no nos permite realizar side-effects, como lo es el data-fetching, y tambien no nos es posible seguir el comportamiento de nuestro estado antes o despues de emitir una acci贸n. Todo lo anterior si es posible con Redux. 馃槈

Asi voy:

Hermoso!!!

por que es una mala pr谩ctica usar el index? hay casos que no tenemos este id presente en la api que otra alternativa hay?

No me qued贸 muy claro el uso de esta linea de codigo, que se est谩 haciendo? que es el payload?
...state,favorites:[...state.favorites,action.payload]

Saludos!

As铆 lo hice yo, usando un poco de la l贸gica de Alexis Ulises y agregandole algunas validaciones por pura est茅tica. Intent茅 tambien pasar todo el HTML a un componente presentacional pero se me hizo un lio de this y de props, si alguien lo hizo comparta el codigo!

import React, { useState, useEffect, useReducer } from "react";

import "./styles/Characters.css";

const initialState = {
  favorites: [],
};

const favoriteReducer = (state, action) => {
  switch (action.type) {
    case "ADD_TO_FAVORITE":
      return {
        ...state,
        favorites: [...state.favorites, action.payload],
      };

    case "REMOVE_FROM_FAVORITE":
      return {
        ...state,
        favorites: [
          ...state.favorites.filter((favorite) => favorite !== action.payload),
        ],
      };

    default:
      return state;
  }
};

const Characters = () => {
  const [characters, setCharacters] = useState([]);
  const [favorites, dispatch] = useReducer(favoriteReducer, initialState);

  useEffect(() => {
    fetch("https://rickandmortyapi.com/api/character/")
      .then((response) => response.json())
      .then((data) => setCharacters(data.results))
      .catch((error) => console.warn("Error in fetch:", error));
  }, []);

  const handleFavorite = (favorite) => {
    dispatch({
      type: !!isCharacterInFavorites(favorite) ? "REMOVE_FROM_FAVORITE" : "ADD_TO_FAVORITE",
      payload: favorite,
    });
  };

  const isCharacterInFavorites = (favorite) =>
    favorites.favorites.find((character) => character.id === favorite.id);

  var favIcon = "https://icons.iconarchive.com/icons/hopstarter/soft-scraps/256/Button-Favorite-icon.png";
  var delIcon = "https://uxwing.com/wp-content/themes/uxwing/download/01-user_interface/red-x.png";
  console.log("Favorites: ", favorites);

  return (
    <>
      <div className="Favorites">
        <h2>{ (favorites.favorites.length === 0) ?  "Add your favorite characters here!" : "Favorites:"}</h2>
          {favorites.favorites.map((favorite) => (
            <li
              key={favorite.id}
              className="FavList"
            >
              <button type="button" onClick={() => handleFavorite(favorite)}>
                <img
                    src={delIcon}
                    alt=""
                    className="Icon delete"
                  />
              </button>
              {favorite.name}
            </li>
          ))}
      </div>

      <div className="Characters">
        {characters.map((character) => (
          <div className="Character" key={character.id}>
            <h2> {character.name} </h2>

            <img src={character.image} alt="Character" />

            <h4> Type: {character.species} </h4>
            <h4> Gender: {character.gender} </h4>
            <h4> Origin: {character.origin.name} </h4>

            <button type="button" onClick={() => handleFavorite(character)}>
              <img
                className="Icon"
                src={!!isCharacterInFavorites(character) ? delIcon : favIcon}
                alt=""
              />
            </button>
          </div>
        ))}
      </div>
    </>
  );
};

export default Characters;

As铆 va quedando, me gusta mucho el curso!

El useReducer es una alternativa al useState, sirven para lo mismo. La recomendaci贸n es que cuando vamos a usar estados simples, que pocas cosas van a cambiar, est谩 bien usar el useState. Pero cuando vamos hacer operaciones m谩s complejas con muchas acciones, es mejor usar un reducer.

No entiendo del todo este del reducer pero intent茅 hacer que a la lista no se pueda ingresar un elemento que ya est茅 en la lista鈥 si alguien lo ve y me una sugerencias si est谩 bien o no se lo agradecer铆a

import { useState, useEffect, useReducer } from 'react'

const initialState = {
    favorites: []
}

const favoriteReducer = (state, action) => { //agregar谩 a favoritos
    console.log(state.favorites, action)
    debugger
    switch (action.type) {
        case 'ADD_TO_FAVORITE':
            if (state.favorites.length === 0) {
                return {
                    ...state,
                    favorites: [...state.favorites, action.payload]
                }
            } else if (state.favorites.some(favorite => favorite === action.payload)) {
                return state
            } else {
                return {
                    ...state,
                    favorites: [...state.favorites, action.payload]
                }
            }

        case 'REMOVE_TO_FAVORITE':
            return {
                ...state,
                favorites: [...state.favorites.filter(favorite => favorite !== action.payload),]
            }
        default:
            return state
    }
}

const Characters = () => {

    const [characters, setCharacters] = useState([])
    const [favorites, dispatch] = useReducer(favoriteReducer, initialState)

    useEffect(() => {
        fetch('https://rickandmortyapi.com/api/character')
            .then(response => response.json())
            .then(data => { setCharacters(data.results) })
    }, [])

    const handleClick = favorite => {
        dispatch({ type: 'ADD_TO_FAVORITE', payload: favorite })
    }

    const handleQuitarClick = favorite => {
        dispatch({ type: 'REMOVE_TO_FAVORITE', payload: favorite })
    }

    return (
        <div className="Characters">

            {favorites.favorites.map(favorite => (
                <li key={favorite.id} >
                    {favorite.name}
                    <button type="button" onClick={() => handleQuitarClick(favorite)}>Quitar</button>
                </li>
            ))}

            {characters.map(item => (
                <div key={item.id} className="CharacterTarget">
                    <h2>{item.name}</h2>
                    <img src={item.image} alt="" />
                    <h3>{item.species}</h3>
                    <button type="button" onClick={() => handleClick(item)}>Agregar a Favoritos</button>
                </div>
            ))}

        </div>
    )
}

export default Characters

En el minuto 7:41 menciona que es mala pr谩ctica utilizar el index del arreglo, 驴Porqu茅? 驴Es mala pr谩ctica usar el segundo par谩metro de la funci贸n map?

Aqu铆 mi avance

Interesante su funcionamiento, muy similar a Redux
Para los que hicimos el curso de Redux esto es piece of cake

Si alguien usa WSL con Ubuntu y no les detecta los cambios cuando corren npm run start deben agregar en la raiz del proyecto un archivo llamado .env y poner CHOKIDAR_USEPOLLING=true y listo pueden volver a correr npm run start 馃槃

Oscar simplemente 20/10 y mas porque es de Coahuila, siuuuu

Resaltar items seleccionados como favoritos
Agrega y remueve favoritos

//Parte inicial de nuestro archivo Cryptos.jsx, mostramos los reducers

import React, { useState, useEffect, useReducer } from "react";
import _ from "lodash";

import CyptoCard from "./Crypto";

const initialState = {
    favorites: [],
};
const favoritesReducer = (state, action) => {
    switch (action.type) {
        case "ADD_TO_FAVS":
            return {
                ...state,
                favorites: [...state.favorites, action.payload],
            };
        case "REMOVE_FROM_FAVS":
            const newFavorites = _.pull(state.favorites, action.payload);
            return {
                ...state,
                favorites: newFavorites,
            };
        default:
            return state;
    }
};
// continuaci贸n de Cryptos.jsx
const Cryptos = () => {
    const [cryptos, setCryptos] = useState([]);
    const [state, dispatch] = useReducer(favoritesReducer, initialState);
    useEffect(() => {
        fetch("https://api.nomics.com/v1/currencies/ticker?key=123456789&per-page=50&page=1")
            .then((res) => res.json())
            .then((data) => setCryptos(data));
    }, []);

    const addToFavs = (newFavorite) => {
        dispatch({ type: "ADD_TO_FAVS", payload: newFavorite });
    };
    const removeFromFavs = (favoriteToBeRemoved) => {
        dispatch({ type: "REMOVE_FROM_FAVS", payload: favoriteToBeRemoved });
    };
    const { favorites } = state
    return cryptos.map((crypto) => {
        const isFav = _.includes(favorites, crypto.symbol);
        return <CyptoCard
            isFav={isFav}
            {...crypto}
            key={crypto.id}
            addToFavs={addToFavs}
            removeFromFavs={removeFromFavs}
        />;
    });
};
export default Cryptos;

Y ahora miraremos el componente que renderiza cada elemento

import React, { useContext } from "react";
import ThemeContext from "../context/ThemeContext";

const Crypto = ({ logo_url, currency, name, symbol, price, isFav, addToFavs, removeFromFavs }) => {
    const { darkModeActive } = useContext(ThemeContext);
    return (
        <div className={`card crypto-card m-3 ${isFav ? "is-fav" : ""}`}>
            <div className="card-content">
                <div className="media">
                    <div className="media-left">
                        <figure className="image is-48x48">
                            <img src={logo_url} alt={`${currency} logo`} />
                        </figure>
                    </div>
                    <div className="media-content">
                        <p className="title is-4">{name}</p>
                        <p className="subtitle is-6">{symbol}</p>
                    </div>
                </div>

                <div className="content is-flex is-flex-direction-row is-justify-content-space-between">
                    <span className={`tag mr-1 ${darkModeActive ? "is-dark" : "is-white"}`}>USD {parseFloat(price).toFixed(2)}</span>
                    <button
                        onClick={() => (isFav ? removeFromFavs(symbol) : addToFavs(symbol))}
                        className={`button  is-small is-danger  ${darkModeActive ? "is-outlined" : ""}`}
                    >
                        <span>{isFav ? `Remover de` : `Agregar a`} Favoritos</span>
                        <span className="icon is-small">
                            <i className="mdi mdi-heart"></i>
                        </span>
                    </button>
                </div>
            </div>
        </div>
    );
};

export default Crypto;

Resultado

Dejo mi aporte de ir un poco m谩s y realizar un bot贸n que elimine al que se encuentra en la lista de favoritos. NOTA: NO TIENE VALIDACI脫N DE VERIFICAR SI EL SELECCIONADO YA SE ENCONTRABA EN FAVORITOS.

import React, { useState, useEffect, useReducer } from 'react'

const initialState = {
    favoritesCharacters: []
};

const favoriteReducer = (state, action) => {
    switch(action.type) {
        case 'ADD_TO_FAVORITES':
            return {
                ...state,
                favoritesCharacters: [...state.favoritesCharacters, action.payload]
            };

        case 'DELETE_FROM_FAVORITES':
            return {
                ...state,
                favoritesCharacters: [
                    ...state.favoritesCharacters.filter( item => item.id !== action.payload.id )
                ]
            };
        
        default:
            return state;
    }
}

const Characters = () => {
    const [characters, setCharacters] = useState([]);
    const [favorites, dispatch] = useReducer(favoriteReducer, initialState)

    const getCharacters = async () => {
        const response = await fetch('https://rickandmortyapi.com/api/character/');
        const data = await response.json();
        const results = data.results;
        setCharacters(results);
    }

    useEffect(() => {
        getCharacters();
    }, [])

    const handleClickOnFavorite = favorite => {
        dispatch({type: 'ADD_TO_FAVORITES', payload: favorite});
    }

    const handleClickOnDeleteFavorite = character => {
        dispatch({type: 'DELETE_FROM_FAVORITES', payload: character})
    }

    return (
        <div className="">
            <div className="Characters">
                {
                    favorites.favoritesCharacters.length > 0 && (
                        <>
                            <div className="Characters-title">
                                <h2>Favorites Characters</h2>
                            </div>
                            <div className="Characters-list">
                                {
                                    favorites.favoritesCharacters.map( favoriteCharacter => (
                                        <div className="Character" key={favoriteCharacter.id}>
                                            <h2>{ favoriteCharacter.name}</h2>
                                            <figure>
                                                <img src={favoriteCharacter.image} alt={favoriteCharacter.species} />
                                                <figcaption>
                                                    <p><b>Gender: </b> {favoriteCharacter.gender}</p>
                                                    <p><b>Origin: </b> {favoriteCharacter.origin.name}</p>
                                                    <p><b>Location: </b> {favoriteCharacter.location.name}</p>
                                                    <p><b>Specie: </b> {favoriteCharacter.species}</p>
                                                    <p><b>Status: </b> {favoriteCharacter.status}</p>
                                                    <p><button type="button" onClick={() => handleClickOnDeleteFavorite(favoriteCharacter)}>Delete</button></p>
                                                </figcaption>
                                            </figure>
                                        </div>
                                    ))
                                }
                            </div>
                        </>
                    )
                }
            </div>
            <div className="Characters">
                <div className="Characters-title">
                    <h2>Characters</h2>
                </div>
                <div className="Characters-list">
                    {
                        characters.map( (character) => (
                            <div className="Character" key={character.name}>
                                <h2>{ character.name}</h2>
                                <figure>
                                    <img src={character.image} alt={character.species} />
                                    <figcaption>
                                        <p><b>Gender: </b> {character.gender}</p>
                                        <p><b>Origin: </b> {character.origin.name}</p>
                                        <p><b>Location: </b> {character.location.name}</p>
                                        <p><b>Specie: </b> {character.species}</p>
                                        <p><b>Status: </b> {character.status}</p>
                                        <p><button type="button" onClick={() => handleClickOnFavorite(character)}>Add to favorites</button></p>
                                    </figcaption>
                                </figure>
                            </div>
                        ))
                    }
                </div>
            </div>
        </div>
    );
};

export default Characters;

ESPERO LES PUEDA AYUDAR

Vaya, este curso lo necesitaba hace 2 a帽os cuando inicie mi aplicaci贸n de React. Excelente!

Aqu铆 mi reducer con la funci贸n de eliminar:

const favoritesReducer = (state, action) => {
    switch (action.type) {
        case 'ADD_TO_FAVS':
            return {
                ...state,
                favorites: [...state.favorites, action.payload]
            }
        case 'REMOVE_FROM_FAVS':
            return {
                ...state,
                favorites: [...state.favorites.filter(c => c.id !== action.payload.id)]
            }
        default:
            return state;
    }
}

Hola, estoy siguiendo este proyecto en Nextjs. Les dejo mi repositorio:

https://github.com/danyel117/platzi-conf-store