En lo personal no recomiendo pasar el nombre de las acciones como texto plano, es muy posible crear typos.
Deberían utilizar un objeto intermediario que maneje cada acción, en mi caso queda algo así:
Actions
Reducers
Bienvenida al curso
Todo lo que aprenderás sobre React Router y Redux
¿Qué es React Router? y Aplicarlo en tus proyectos
¿Qué es React Router y cómo instalarlo?
Crear nuestro archivo de Rutas
Container: Login
Container: Register
Container: 404 Not Found
Componente: Layout
Manejando enlaces y configuraciones
¿Qué es Redux?
Qué es Redux
¿Qué es Redux? Profundizando en la herramienta
Instalación de Redux
Creando el Store de Redux
Aplicando Redux a nuestra aplicación
Creando los reducers
Finalizando reducers y eliminar favoritos
Debuggeando nuestro proyecto (agregando validaciones a nuestro componente card)
Crear Formulario de Login
Formulario de Login con Redux
Creando un Servicio para Gravatar
Uso de gravatar en nuestro proyecto
Validación para LogIn LogOut
Register
Register con Redux
Vista general del player
Arreglando la funcionalidad del player
Terminando de detallar nuestro player
Validaciones
Validaciones de UI
Debug con Redux Devtools
Cierre del curso
Cierre del Proyecto
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Oscar Barajas Tavares
Aportes 48
Preguntas 12
En lo personal no recomiendo pasar el nombre de las acciones como texto plano, es muy posible crear typos.
Deberían utilizar un objeto intermediario que maneje cada acción, en mi caso queda algo así:
Actions
Reducers
Recomendación: Ulilizar constantes en Redux.
Es recomendable usar constantes en redux, por ejemplo, crear un archivo que se llame “actionTypes.js”, y dentro colocar lo siguiente:
export const SET_FAVORITE = 'SET_FAVORITE'
export const DELETE_FAVORITE = 'DELETE_FAVORITE'
Estas constantes las importaremos en los actions y en los reducers.
¿Con qué fin se usan estas constantes? Muchos no le encuentran sentido porque el valor tiene el mismo nombre que la constante, pero los beneficios son los siguientes.
Espero que les haya servido de algo. No es algo que yo me haya inventado, muchos manejan esa escructura. Si gustan, aquí les dejo más información:
Ha sido doloroso pero reconfortante saber que he entendido. Por favor corrijanme lo que sea necesario. Es complejo entender el patrón de diseño de Flux, pues todo es archivos separados, pero tomando el tiempo de entender todo es legible. Les dejo mi comprensión, espero les aydue a quiénes no la logran de primera:
.
// index.js
const initialStore = { /* ... */ }
const store = createStore(reducer, initialState);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app'),
);
1.1. El reducer va a recibir el estado inicial que se pasa en createStore , pero también, en su función recibirá la acción que debe hacer cuando ocurra un llamado al Store.
.
2. Dentro del reducer vamos a devolver el estado actualizado del Store únicamente cuando ocurra una acción, cuando se despache una acción . Dependiendo de la acción el estado del store se actualiza o se queda igual, sea agregar a favorito o crear un estado inicial al ingresar a Home, respectivamente por ejemplo.
2.1. El payload es lo que vamos a enviarle a la acción, que recibirá el reducer, para actualizar el Store o actualizar el estado.
// actions.js
export const setFavorite = (payload) => ({
type: 'SET_FAVORITE',
payload,
});
export const deleteFavorite = (payload) => ({
type: 'DELETE_FAVORITE',
payload,
});
2.2. Cuando el reducer recibe ese payload por medio del action, podemos hacer algo con la informació, en este caso, se actualiza la lista myList
// reducer.js
const reducer = (state, action) => {
switch (action.type) {
case 'SET_FAVORITE':
console.log('action -> ', action);
return {
...state,
myList: [
...state.myList.filter((item) => item.id !== action.payload.id),
action.payload,
],
};
case 'DELETE_FAVORITE':
console.log('state.myList -> ', state.myList);
return {
...state,
myList: state.myList.filter((item) => item.id !== action.payload),
};
default:
return state;
}
};
export default reducer;
.
3. Al tener definida la estructura del Store, del reducer y de las acciones, podemos definir funciones para manejar estas acciones directamente en el UI --> Renderizamos la información en componente Home:
3.1. Conectamos Home al Store por medio de connect():
// Home.jsx
export default connect(mapStateToProps, null)(Home);
3.2. Definimos la información que debe traer Home desde el store, es por eso que se define mapStateToProps , pues será el argumento que trae del estado de Store la información que necesita el componente, y pasa como PROPIEDADES al mismo componente:
// Home.jsx
const mapStateToProps = (state) => {
return {
myList: state.myList,
trends: state.trends,
originals: state.originals,
};
};
3.3. Se pasan las propiedades a Home:
// Home.jsx
const Home = ({ myList, trends, originals }) => {
return (
<>
<Search />
{ myList.length > 0 && (
<Categories title='Mi lista'>
<Carousel>
{myList?.map((item) => {
return (
<CarouselItem key={item.id} {...item} />
);
})}
</Carousel>
</Categories>
)}
<Categories title='Originals'>
<Carousel>
{originals?.map((item) => {
return (
<CarouselItem key={item.id} {...item} />
);
})}
</Carousel>
</Categories>
<Categories title='Trends'>
<Carousel>
{trends?.map((item) => {
return (
<CarouselItem key={item.id} {...item} />
);
})}
</Carousel>
</Categories>
</>
);
};
// CarouselItem
const mapDispatchToProps = {
setFavorite,
deleteFavorite,
};
export default connect(null, mapDispatchToProps)(CarouselItem);
4.2. El objeto mapDispatchToProps trae las ACCIONES. Se realiza puesto que en cada CarouselItem vamos a ejecutar una acción que modificará el Store -> Estas acciones también se podrán llamar por medio de los PROPS del componente. Logrando que podamos definir los manejadores de estas acciones:
// CarouselItem.jsx
const CarouselItem = (props) => {
const { id, cover, title, year, contentRating, duration } = props;
const handleSetFavorite = () => {
props.setFavorite({
id, cover, title, year, contentRating, duration,
});
};
const handleDeleteFavorite = () => {
props.deleteFavorite(id);
};
return (
!id ? (
<>
<p>La lista está vacía</p>
</>
) :
(
<>
<div className='Carousel--item'>
<img className='Carousel--item__img' src={cover} alt={title} />
<div className='Carousel--item__details'>
<p className='Carousel--item__details--title'>{title}</p>
<p className='Carousel--item__details--subtitle'>
{`${year} | ${contentRating} | ${duration}`}
</p>
<div className='Carousel--item__details--buttons'>
<img src={playIcon} alt='Play' />
<img src={plusIcon} alt='Plus' onClick={handleSetFavorite} />
<img src={removeIcon} alt='Delete' onClick={handleDeleteFavorite} />
</div>
</div>
</div>
</>
)
);
};
CarouselItem.propTypes = {
id: PropTypes.number,
cover: PropTypes.string,
title: PropTypes.string,
year: PropTypes.number,
contentRating: PropTypes.string,
duration: PropTypes.number,
};
Renderizando cada uno de los items que existen en el Store.
.
5. El render inicial hará que existan listas de Trends y Originals, pero cada uno de los CarouselItem tendrá una acción: setFavorite y deleteFavorite. Estas acciones manipulan el Store definiendo los estados para la lista de favoritos (myList).
🦄 Acá todo el código de la clase hasta este momento:
https://github.com/AndresCampuzano/React-Router-and-Redux/commit/a3a4ae8f9addc5807b5421dc30be793fed16a639
El método filter() crea un nuevo array con todos los elementos que cumplan la condición implementada por la función dada.
Una sugerencia para que no se agregue el mismo elemento varias veces a myList puede ser:
Yo escribi la funcion handleDeleteFavorite de la siguiente manera:
const handleDeleteFavorite = () => {
props.deleteFavorite(id);
};
Y en el onclick de remove quedo asi:
onClick={handleDeleteFavorite}
Ya que estamos descomponiendo el valor id de props, no veo la razon de crear una arrow function en el evento onclick para pasarle el valor del id.
al tomar está carrera pude aprender que a cada momento podemos adquirir nuevos conocimientos… me pude sorprender de lo que se puede hacer con javascript
Genial el uso de Redux, es complicado pero con la práctica todo se aprende
Práctica: my prefect code.
Estuve viendo el Curso de Redux por Bedu y no lo logre terminarlo por la cantidad de vacios que estaba teniendo con respecto a algunos componentes de Redux… llevo estas pocas clases con el profesor Oscar y porfin logré sintetizar de una manera muy facil y entendible (para mi) el uso de mapStateToProps y mapDispatchToProps…
Respeto y admiración total por la forma tan fácil en la que enseña el profesor ☝
Espero no ser el único que con cada clase que termino se me dibuja una sonrisa!! animo chicos y chicas dentro de poco vamos a crear cosas muy interesantes!!
Para los amantes de los iconos comparto
Good Class!!!
…
…
como podriamos hacer para que el remove-icon solo se muestre en myList y no en todas las secciones?
usando hooks
dispatch(deteleFavorite(itemId));```
Tip: Si quieren que los iconos cambien, por ejemplo que el de eliminar sólo aparezca en Mi lista: Lo pueden hacer con un ternario:
const hasListItem = hasItem != undefined
?
<img src={iconDelete} alt="icono delete" onClick={() => handleDeleteFavorite(id) }/>
:
<img src={iconPlus} alt="icono play" onClick={handleSetFavorite} />;```
Este es mi codigo para carouseItem para eliminar el plus de mylist y agregar en su lugar el remove
/* eslint-disable import/prefer-default-export */
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { setFavorite, deleteFavorite } from '../../redux/actions';
import '../../assets/styles/CarouselItem.scss';
import playIcon from '../../assets/static/play-icon.png';
import plusIcon from '../../assets/static/plus-icon.png';
import removeIcon from '../../assets/static/remove-icon.png';
const CarouselItem = (props) => {
const { id, cover, title, year, contentRating, duration, listType } = props;
const handleSetFavorite = () => {
props.setFavorite({
id, cover, title, year, contentRating, duration, listType: 'myList',
});
};
const handleDeleteFavorite = (itemId) => {
props.deleteFavorite(itemId);
};
const button = (listType !== 'myList') ?
<img className='carousel-item__details--img' src={plusIcon} alt='Plus Icon' onClick={handleSetFavorite} /> :
<img className='carousel-item__details--img' src={removeIcon} alt='remove Icon' onClick={() => handleDeleteFavorite(id)} />;
return (
<div className='carousel-item'>
<img className='carousel-item__img' src={cover} alt={title} />
<div className='carousel-item__details'>
<div>
<img className='carousel-item__details--img' src={playIcon} alt='Play Icon' />
{ button }
</div>
<p className='carousel-item__details--title'>{title}</p>
<p className='carousel-item__details--subtitle'>
{`${year} ${contentRating} ${duration}`}
</p>
</div>
</div>
);
};
CarouselItem.propTypes = {
cover: PropTypes.string,
title: PropTypes.string,
year: PropTypes.number,
contentRating: PropTypes.string,
duration: PropTypes.number,
};
const mapDispatchToProps = {
setFavorite,
deleteFavorite,
};
export default connect(null, mapDispatchToProps)(CarouselItem);
super, viendo la case nuevamente, se entiende que bien
según yo el bug es que cuando el CarouselItem esta en “Mi lista” no debería mostrarse el icono de agregar y cuando esta en las demás secciones no debería mostrarse el icono de eliminar
Echenle un ojo a esto: https://www.sololearn.com/
Ahi hay cursos de todo. El de “React + Redux” me sirvio para esclarecer conceptos que estaban con alfiler. Saludos
Les recomiendo echar un vistado al paquete immutability-helper, ayuda muchísimo cuando tenemos objetos o arreglos con varios niveles de anidación, además se me hace mucho más sencillo su código.
ya le voy entendiendo a lo de redux, creo que falta un poco mas de react en la ruta de javascript
Para no tener problema con los type es mejor crear un archivo donde podeamos tener mayor control de ellos
export const ADD_ITEM = 'ADD_ITEM';
export const DELETE_ITEM = 'DELETE_ITEM';
luego usarlo en los action como en el reducer
export const setFavorite = payload => ({
type: type.ADD_ITEM,
payload
});
case type.ADD_ITEM:
El bug es que estamos agregando el icono a todos los items cuando solo lo debe de tener los de mylist
En esta ocasión recomiendo crear un componente diferente para myList.
No necesitamos que este el botón de agregar a mi lista y tampoco necesitamos y no queremos que en el resto de las listas este el botón de borrar de la lista.
Creo que lo mejor seria crear un componente para myList igual al resto pero intercambiando el botón de agregar a la lista por el de borrarlo.
Díganme si hay una mejor forma de solucionarlo.
La descripción de los action puedes intentar dejarlo en un archivo Json, después lo que harías es importar este archivo Json en el archivo reducer y en el archivo de los action, así solo el cambio depende de un archivo
creado el delete en actions
Buena clase!
no se elimina el item, tampoco me sale ningun error…
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { setFavorite, deleteFavorite } from '../actions';
import '../assets/styles/components/CarouselItem.scss';
import playIcon from '../assets/static/play-icon.png';
import plusIcon from '../assets/static/plus-icon.png';
import removeIcon from '../assets/static/remove-icon.png';
const CarouselItem = (props) => {
const { id, cover, title, year, contentRating, duration } = props;
const handleSetFavorite = () => {
props.setFavorite({
id,
cover,
title,
year,
contentRating,
duration,
})
}
const handleDeleteFavorite = itemId => {
props.deleteFavorite(itemId);
}
return (
<div className="carousel-item">
<img className="carousel-item__img" src={cover} alt={title} />
<div className="carousel-item__details">
<div>
<img className="carousel-item__details--img"
src={playIcon}
alt="Play Icon"
/>
<img className="carousel-item__details--img"
src={plusIcon}
alt="Plus Icon"
onClick={handleSetFavorite}
/>
<img className="carousel-item__details--img"
src={removeIcon}
alt="Remove Icon"
onClick={()=>handleDeleteFavorite(id)}
/>
</div>
<p className="carousel-item__details--title">{title}</p>
<p className="carousel-item__details--subtitle">
{`${year} ${contentRating} ${duration}`}
</p>
</div>
</div>
);
}
CarouselItem.propTypes = {
cover: PropTypes.string,
title: PropTypes.string,
year: PropTypes.number,
contentRating: PropTypes.string,
duration: PropTypes.number,
}
const mapDispatchToProps = {
setFavorite,
deleteFavorite,
};
export default connect(null, mapDispatchToProps)(CarouselItem);```
Habia puesto myList en un lado con l minuscula y no funcionaba, pero tampoco daba error xD
A mi me funciona enviando directamente el id a deleteFavorite, sin pasarlo por parametros ni nada, ¿es mala practica??
const CarouselItem = (props) => {
const { cover, title, year, contentRating, duration, id } = props;
const handleDeleteFavorite = () => {
props.deleteFavorite(id);
};
<img src={deleteIcon} onClick={handleDeleteFavorite} />```
Muy buena clase, y probando la aplicación detecte el bug jeje
exploto mi cabeza en esta clase ajjaja, changos tengo que verlo muchas veces
Se utiliza el método filter para crear un nuevo Array con todos los elementos que cumplan con la condición implementada en la función
Todo correcto hasta el momento, me sirvió de mucho el curso introductorio de JavaScript. Sin embargo, recuerdo con Leonidas que usaba una librería para no mutar el estado de la aplicación y con ella todo perdío sentido porque manejaba muchos errores y segun yo estaba todo bien.
Tengo una consulta sobre el payload…
estube viendo que en otros casos SI se le pasa informacion al mismo
ej
payload{
date:12-12-11,
country: eeuu,
}
LO QUE NO ENTIENDO ES PORQUE PASAMOS EL PAYLOAD VACIO
hola comunidad, personalmente se me dificultó mucho entender e implementar el connect, por lo que googlenado encontre una formas mas practica para reemplazar connect, con UseDispatch y UseSelector https://levelup.gitconnected.com/react-redux-hooks-useselector-and-usedispatch-f7d8c7f75cdd
Que increíble es entender esto, traté de estudiar lo mismo en otro lado y no entendí como aquí, gracias Oscar de verdad.
Me encanto la parte de agregar id
Un poco confuso para mi pero no nos rendiremos!
Como se trabaja con imagenes wepb?
Tengo un problema para implementar la funcionalidad de eliminar favoritos, tengo un error en el método filter y no he logrado encontrarlo. Este es el link del repositiorio en github: https://github.com/wilmarFlorez/w-video
Hola! Yo lo diseñé para que el botón de eliminar de favoritos solo aparezca cuando el item esta en la lista de “myList”
Toda la lógica la maneje desde el action de setFavorite, les muestro como lo hice:
export const setFavorite = favorite => (dispatch, getState) => {
// Other way to update the list
const { myList } = getState().videosReducer;
const updatedList = [...myList, favorite];
const updatedListItem = updatedList.map(item => ({
...item,
favorite: !!(favorite.id),
}));
dispatch({
type: SET_FAVORITE,
payload: updatedListItem,
});
};
Por que no funciona de esta manera?:
<img
className='carousel-item__details--img'
src={removeIcon}
alt='Remove Icon'
onClick={handleDeleteFavorite(id)}
/>
Por se debe asignar la función handleDeleteFavorite(id), usando un arrow function?
<img
className='carousel-item__details--img'
src={removeIcon}
alt='Remove Icon'
onClick={() => handleDeleteFavorite(id)}
/>
No entendi que es payload
usando Dispatch
const handleDeleteFavorite = () => {
dispatch(deleteFavorite(id))
};
onClick={handleDeleteFavorite}
Vamos a empezar a crear en ACTIONS y crear el que va a describir lo que vamos a hacer, en este caso lo uqe vamos a eliminar, quedaria igual que la anterior:
export const deleteFavorite = payload => ({
type: 'DELETE_FAVORITE',
payload,
})
Ahora en nuestro reducer vamos a crear un nuevo caso del switch en el cual vamos a devolver en el return lo que vamos a trabajar para nuetro estado. Siempre vamos a trabajar con el estado que ya tenemos y manejamos mylist. Lo que haremos es crear en nuestra lista crear un nuevo array que cumplan una condicion dada con ayuda del metodo filter. Esto evaluando con el valor de ID.
case 'DELETE_FAVORITE':
return{
...state,
mylist: state.mylist.filter(items => items.id !== action.payload),
}
Ahora tenemos que modificar nuestro CarouselItem, primero tenemos que importar la funcion de deletefavorite:
import { setFavorite, deleteFavorite } from '../actions';
Al igual que ir agregando el icono que se encargara de esto. Tambien tenemos que agregarlo a nuestro DispatchToProps
const mapDispatchToProps = {
setFavorite,
deleteFavorite,
}
Ahora crearemos nuestra funcion handleDeleteFavorite ala cual le pasaremos de parametro el ID:
const handleDeleteFavorite = (itemId) => {
props.deleteFavorite(itemId);
}
Este ID lo tenemosq ue recibir de los props el cual lo habiamos ignorado, solo tenemos que agregarlo en todos lados donde destructuramos estos props:
const { id, cover, title, year, contentRating, duration } = props;
Ahora para llamar la funcion pondremos en nuestra nueva imagen:
<img src={deleteicon} alt="Plus Icon" onClick={() => handleDeleteFavorite(id)} />
Esto lo llamamos de distinta forma ya que neceistamos agregar este ID que asignamos para poderlo identificar y eliminarlo de nuestro estado.
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?