No tienes acceso a esta clase

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

Curso de React Router 5 y Redux

Curso de React Router 5 y Redux

Oscar Barajas Tavares

Oscar Barajas Tavares

Finalizando reducers y eliminar favoritos

14/29
Recursos

Aportes 48

Preguntas 12

Ordenar por:

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

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.

  • Ayuda a mantener una consistencia y evita los errores al escribir código, ya que si escribes Strings en las actions y en los reducers puede que en una de esas veces te equivoques.
  • Algunas veces el usuario desea ver qué actions existen antes de trabajar en alguna. Si trabajas en un equipo puede que alguien la haya implementado. Tener todas las actions como constantes en un archivo te facilitará esa búsqueda.
  • La lista de_ action types_ que fueron añadidas, eliminadas o actualizadas en un _Pull Request _ayuda a todo el equipo a visualizar rápidamente qué cambios se están haciendo en la funcionalidad.
  • Si cometes un error (typo) cuando importas tus constantes, obtendrás undefined cuando intentes ejecutar tu código. Es más fácil encontrar este error de variable que intentando descifrar por qué no pasa nada cuando ejecutas tu action.

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:
.

  1. Se alimenta la aplicación por medio del Provider cuando definimos el createStore(reducer, initialState). Ésto le enviará información al componente que necesite acceder al Store.
// 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>

    </>
  );
};
  1. Estas propiedades las recibe el componente CarouselItem y serán las que pinten una película. sea en la lista de Trends u Originals. 4.1. Para que reciba props, se debe conectar el componente al Store:
// 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).

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.