🧠⚛️ useReducer
no es una alternativa a Redux, solo “toma prestado” el uso de reducers para actualizar nuestro estado local.
¡Bienvenida! Este es un curso especial de React Hooks
¿Qué aprenderás en el Curso Profesional de React Hooks?
¿Qué son los React Hooks y cómo cambian el desarrollo con React?
Introducción a React Hooks
useState: estado en componentes creados como funciones
useEffect: olvida el ciclo de vida, ahora piensa en efectos
useContext: la fusión de React Hooks y React Context
useReducer: como useState, pero más escalable
¿Qué es memoization? Programación funcional en JavaScript
useMemo: evita cálculos innecesarios en componentes
useRef: manejo profesional de inputs y formularios
useCallback: evita cálculos innecesarios en funciones
Optimización de componentes en React con React.memo
Custom hooks: abstracción en la lógica de tus componentes
Third Party Custom Hooks de Redux y React Router
Configura un entorno de desarrollo profesional
Proyecto: análisis y retos de Platzi Conf Store
Git Hooks con Husky
Instalación de Webpack y Babel: presets, plugins y loaders
Configuración de Webpack 5 y webpack-dev-server
Configuración de Webpack 5 con loaders y estilos
Loaders de Webpack para Preprocesadores CSS
Flujo de desarrollo seguro y consistente con ESLint y Prettier
Estructura y creación de componentes para Platzi Conf Store
Arquitectura de vistas y componentes con React Router DOM
Maquetación y estilos del home
Maquetación y estilos de la lista de productos
Maquetación y estilos del formulario de checkout
Maquetación y estilos de la información del usuario
Maquetación y estilos del flujo de pago
Integración de íconos y conexión con React Router
Integración de React Hooks en Platzi Conf Merch
Creando nuestro primer custom hook
Implementando useContext en Platzi Conf Merch
useContext en la página de checkout
useRef en la página de checkout
Integrando third party custom hooks en Platzi Conf Merch
Configura mapas y pagos con PayPal y Google Maps
Paso a paso para conectar tu aplicación con la API de PayPal
Integración de pagos con la API de PayPal
Completando la integración de pagos con la API de PayPal
Paso a paso para conectar tu aplicación con la API de Google Maps
Integración de Google Maps en el mapa de checkout
Creando un Custom Hook para Google Maps
Estrategias de deployment profesional
Continuous integration y continuous delivery con GitHub Actions
Compra del dominio y despliega con Cloudflare
Optimización de aplicaciones web con React
Integración de React Helmet para mejorar el SEO con meta etiquetas
Análisis de performance con Google Lighthouse
Convierte tu aplicación de React en PWA
Bonus: trabaja con Strapi CMS para crear tu propia API
Crea una API con Strapi CMS y consúmela con React.js
¿Qué sigue en tu carrera profesional?
Próximos pasos para especializarte en frontend
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Aportes 72
Preguntas 21
🧠⚛️ useReducer
no es una alternativa a Redux, solo “toma 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
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
Así va la app
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…
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;
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.
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>
);
};
Les comparto como va quedando mi plataforma.
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
}
}
Siento que el las explicaciones estan siendo muy superficiales… No todo se aprende a puro ejemplo
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 **‘Character’ ** dentro de context, y esa carpeta contiene ‘CharacterContext.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 😄
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 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.')
}
};
Dejo un tutorial a lo visto en está clase:
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.
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.
//Estado inicial del reducer
const initialState = {
favorites:[],
}
const reducer = (state, action)=>{
switch(action.type){
case 'ADD_TO_FAVORITE':
return{
...state,
favorites:[...state.favorites, action.payload]
}
default:
return state;
}
}
Los siguientes pasos requieren se ejecuten dentro del componente
const [state, dispatch] = useReducer(reducer, initialState);
const hadleClick = character =>{
dispatch({
type: 'ADD_TO_FAVORITE',
payload:character
})
}
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:
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}
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>
)
}
: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);
}
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 “eventHandler” (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.
he leído varios comentarios y tengo una opinión similar. L as explicaciones se quedan cortas para explicar los hooks
Les comparto mi implementación del use reducer ya con CSS y un poco mas de lógica para agregar y quitar personajes de los favoritos
El codigo con algunas notas lo pueden encontrar en mi repositorio https://github.com/Hiddenberg/react-hooks-platzi/tree/v0.1
Este es mi reducer, le agregué una decisión para evitar agregar el mismo personaje favorito más de una vez. Lo hice con filter… Agradecería me hagan saber si hay una forma mejor de evitar que se agregue un personaje favorito más de una vez.
<code>
const initialState = {
myFavorites: []
};
const favoriteReducer = (state, action) => {
switch (action.type) {
case "ADD_TO_FAVORITES":{
if(state.myFavorites.filter(fav => fav.id === action.payload.id).length){
return state;
} else {
return {
...state,
myFavorites: [...state.myFavorites, action.payload]
}
}
}
default:
return state;
}
}
function App() {
const [personajes, setPersonajes] = useState([]);
const [favorites, dispatch] = useReducer(favoriteReducer, initialState);
useEffect(() => {
/************
* Se cargan los datos de los personajes de la api
* Rick and Morty
* ...
* ...
* }, []);
*************/
const handleAddToFavorite = (favorite) => {
dispatch({ type: "ADD_TO_FAVORITES", payload: favorite});
}
return (
<div className="personajes">
<h1>Personajes de Rick and Morty</h1>
<hr />
{favorites &&
<div className="favoritos">
<h2>Personajes favoritos</h2>
<ul>
{favorites.myFavorites.map(favorite => {
return (
<li key={favorite.id}>
{favorite.name}
</li>
)
})}
</ul>
<hr />
</div>
}
{personajes.map(personaje => (
<h2 key={personaje.id}>
{`${personaje.name} - ${personaje.type ? personaje.type : "Unknown"} `}
<button type="button" onClick={() => handleAddToFavorite(personaje)}>
Agregar a favoritos
</button>
</h2>
))}
</div>
);
}
export default App;
</code>
No explican casos o usos mas comunes. muy basico!! para ser un curso profesional
Hasta ahora me va así (No soy diseñador :c, se que se ve mal)
si les pasó como a mi que es la primera vez que ven useReducer, action, dispatch etc este video complementara un poco esta clase.
espero les ayude 😃
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.
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:
no lo se me parece mejor usar redux
Por muy por encima que esté haciendo los estilos y funciones básicas para que nada se rompa, me está gustando cómo va quedando
Lo malo es que recién ahora me di cuenta que me volvió a pasar que trabajo en el diseño para que todo ande y esté bonito (Para lo que suelo hacer), y luego resulta que he estado trabajando todo el tiempo con el zoom modificado 😅
useReducer es un hook que permite agregar un reducer a un componente.
La sintaxis de useReducer es:
const [state, dispatch] = useReducer(reducer, initialState);
Donde:
Un reducer es una función que toma el estado actual y una acción como argumentos, y devuelve el estado modificado u original según indique la acción.
Una acción o action es un objeto que describe que ha ocurrido en la app y como debe cambiar el estado. Es una acción del usuario en la UI. Puede tener cualquier forma, pero normalmente tiene dos propiedades:
El flujo de useReducer se puede describir así:
Llegamos, implementamos useReducer y chimpún. Es que no se explica bien en ningún momento porqué. Qué es. Qué problema viene a solucionar. Por qué no puedo hacer esto mismo con las herramientas que ya tenía antes. Así no se aprende, tan sólo hacemos cosas porque sí.
¿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
El hook de useReducer es una excelente manera de administrar objetos de estado complejos y transiciones de estado, para manejar estados más simples se usa useState
GENTE QUE PIENSA QUE EL PROFESOR NO EXPLICA:
Este es un curso nivel avanzado de react hooks, practicamente se está dando un repaso de todo lo que se vió en la ruta de React.js, para entender mejor, YA deben saber los conceptos de cursos anteriores, no esperen un curso “profesional” si no tienen la capadidad de comprenderlo.
Entendí un poco mejor con este video:
.
https://www.youtube.com/watch?v=Qisq6I_buBo&list=PLvq-jIkSeTUZ5XcUw8fJPTBKEHEKPMTKk&index=84
.
Explican el paso a paso y como funciona.
No se entiende, no tiene mucho sentido ver una clase en donde no se explique en detalle para que sirve cada Hook. Falta mucha profundizacion, teniendo en cuenta casos de usos.
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!
Aquí podrían revisar mas a fondo useReducer, implementando best practices:
Me gusta, pero lo siento muy desordenado. Con Redux queda más claro el código o es mi percepción?
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.
 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
💡 INFO
Función alternativa auseState
que se vincula a un reducer, de Redux, retornando una referencia alstate
y a la funcióndispatch
.
.
useReducer
se suele preferir por sobre useState cuando el state presenta una lógica compleja:
state
local al state
manejado por Redux..
const [state, dispatch] = useReducer(reducer, initialArg, funcInit);
Su API, expone 3 argumentos donde:
reducer
- el cuál deberá ser del tipo (state, action) => newState
inicitalArg
- ya sea proporcionado por el mismo componente o heredado por el ReduxfuncInit
- función para delegar el proceso de inicialización, el comportamiento será Lazy.
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 instalarreact-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>
);
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!!!
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:
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?