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

Terminando de detallar nuestro player

25/29
Recursos

Aportes 78

Preguntas 12

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

Hola!

Implemente la soluci贸n aplicando useLayoutEffect.

Seg煤n la documentaci贸n funciona igual que useEffect, pero renderiza de forma s铆ncrona.

import React, { useLayoutEffect } from 'react';

const Player = (props) => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;

  useLayoutEffect(() => {
    props.setVideoSource(id);
  }, []);

  return hasPlaying ? (
    <section className='player'>
      <video className='player__item' controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='player__back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </section>
  ) : <NotFound />;
};

Agregando un loading mediante el hook useState.

const Player = ({ history, match, playing, getVideoSource }) => {
  const { id } = match.params;
  const [loading, setLoading] = useState(true);
  const hasPlaying = Object.keys(playing).length > 0;

  useEffect(() => {
    getVideoSource(id);
    setLoading(false);
  }, []);

  const handleBtnBack = () => history.goBack();

  if (loading) {
    return <h2>Cargando...</h2>;
  }

  return hasPlaying ? (
    <div className='player'>
      <video controls autoPlay>
        <source src={playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={handleBtnBack}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <Redirect to='/404/' />
  );
};

otra forma de hacer la logica del reducer:

state.trends.concat(state.originals).find(item => item.id === Number(action.payload)) || []

basicamente se usa el metodo concat para unir los 2 arrays y luego el metodo find para filtrar.

El link del source de los videos ya no es publico, al menos a mi me dio un error 401 al intentar llegar a ellos, por lo que les dejo esta opcion
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
solo deben de cambiar el _source _ de cada video en el initialState del archivo /src/index.js

Hola , buenas noches. Compa帽eros mi solucion fue colocarle un setTimeout al Redirect , asi siempre va a cargar el video antes que llevarme al error.

import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getVideoSource } from "../actions/index";
import { Redirect } from "react-router-dom";
import "../assets/styles/components/Player.scss";
const Player = props => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div className="Player">
      <video controls autoPlay>
        <source src={props.playing.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button
          type="button"
          onClick={() => {
            props.history.goBack();
          }}
        >
          Regresar
        </button>
      </div>
    </div>
  ) : (
      setTimeout(<Redirect to="/404/" />, 1000)
    );
};
const mapStateToProps = state => {
  return {
    playing: state.playing
  };
};

const mapDispatchToProps = {
  getVideoSource
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Player);

Para los que no pueden ver los videos porque ya no est谩n disponibles, aqu铆 dejo mi store con videos disponibles, espero que no los deshabiliten.
Ingresen al link para verlo
https://stackblitz.com/edit/platzi-redux-router-store-videos?file=index.js

Para el bug que se presenta, me tom贸 un poco de tiempo entender por qu茅 suced铆a y en unos comentarios anteriores un compa帽ero da una soluci贸n muy l贸gica, que soluciona el problema de forma concreta. Sucede que el hook useEffect funciona asincronamente por decirlo as铆, es decir que cuando la ejecuci贸n va en la l铆nea del useEffect, antes de que termine o mientras se ejecuta, la ejecuci贸n continua.

Inicialmente (primera ejecuci贸n) el playing es {} (Objeto vac铆o), es decir que en primera instancia el hasPlaying es false.

Cuando llega la ejecuci贸n al return el hasPlaying a煤n es vac铆a, pero como 鈥渁l mismo tiempo鈥 se estaba ejecutando el useEffect, 茅ste cambi贸 el estado, por eso se vuelve a renderizar y en este nuevo renderizado el hasPlaying ya es true, porque encontr贸 el objeto que buscaba, entonces ya se renderiza normal, por eso sucede como esa intermitencia de que se alcanza a ver el 404 por peque帽铆simo instante. Ahora bien todo esto ocurre en una fracci贸n de segundo, por eso puede que sea casi que imperceptible.

La soluci贸n que el compa帽ero en un comentario da es que se use el hook useLayoutEffect funciona igual al useEffect solo que lo que har谩 es que 茅ste se ejecute de forma s铆ncrona, es decir la ejecuci贸n no llega al return si el useLayoutEffect no ha terminado de ejecutarse o sea, no se muestra nada y como modifica el estado vuelve y renderiza con eso ya tenemos el hasPlaying true o en el peor de los casos false.

Haga de cuenta de useLayoutEffect es como si tuviera async/await.

const Player = ({ match, playing, getVideoSource, history }) => {

	const { id } = match.params

	const hasPlaying = Object.keys(playing).length > 0

	useLayoutEffect(() => {
		getVideoSource(id)
	}, [])

	return hasPlaying ? (
		<div className='Player'>
			<video controls autoPlay>
				<source src={playing.source} type='video/mp4' />
			</video>
			<div className='Player-back'>
				<button type='button' onClick={() => history.goBack()}>
					Regresar
				</button>
			</div>
		</div>
	) :
		<NotFound />
}

As铆 es como lo he entendido, si estoy equivocado en algo, por favor h谩zmelo saber, y si no me has entendido tambi茅n para intentar de otra forma.

Una soluci贸n podr铆a ser tratar los 2 eventos por separado: cargar video y video no encontrado:

import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux'
import { getVideoSource } from '../actions'

import NotFound from './NotFound'

import '../assets/styles/components/Player.scss'

const Player = props => {
  const { id } = props.match.params
  const [ loading, setLoading ] = useState(true)
  const hasPlaying = Object.keys(props.playing).length > 0

  useEffect(() => {
    props.getVideoSource(id)
    setLoading(false)
  }, [])

  if (loading) return <h2>Cargando video...</h2>
  
  return hasPlaying ? (
		<div className="player">
			<video controls autoPlay>
				<source src={props.playing.source} type="video/mp4" />
			</video>
			<div className="Player-back">
				<button type="button" onClick={() => props.history.goBack()}>
					Regresar
				</button>
			</div>
		</div>
	) : <NotFound />
}

const mapStateToProps = state => (
  {
    playing: state.playing
  }
)

const mapDispatchToProps = {
	getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player)

con ECMA script 6 podemos parsear strings a numeros anteponiendo el signo +, para este caso seria: item.id === +action.payload

Pens茅 que algo estaba mal, puesto que siempre me reproduc铆a el mismo video

Hola yo hago lo siguiente :

-Dejo en null el atributo playing para ser mucho mas exacto en que no se ha cargado
absolutamente nada.
-La idea es que una vez que en el REDUCER se intente buscar el video,tendra como valor un objeto con o
sin atributos.
En el momento que tenga un valor(objeto) lo considero que ya se ha cargado el video y ahora lo que hago es que si esta vacio, quieres decir que no se encontro,y si es todo lo contrario retorno el componente ya que si se habra encontrado.

const initialState = {
	playing : null,
	...
}

En el componente :

const media = ({id,playing,setPlaying}) => {
   useEffect(() =>{
     setPlaying(id)
   },[])

  const render = () => {
    if(!playing) return <h3>cargando...</h3>
    if(!Object.keys(playing).length) return <h3>No existe el video</h3>
    return (
       <div className='video-player'>
		<video
         src={playing.source} 
         controls
	      autoPlay
         />
       </div>
    )
  }
}

Esta es la clase que mas me ha costado del curso 馃槙

a mi no me reproduc铆a el video. Parece que la URL es prvada y no permite visualizar.

Pod茅is sino usar alg煤n video de stock y sustituirlo en el index.js principal.

os dejo uno si alguien necesita
https://static.videezy.com/system/resources/previews/000/044/479/original/banana.mp4

Yo utilice useState.

<code>
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { getVideoSource } from '../actions';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
  const { id } = props.match.params;
  const [loading, changeLoading] = useState(true);
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
    changeLoading(false);
  }, []);

  return loading ?
    (<h1>Loading...</h1>) :
    hasPlaying ? (
      <div className='Player'>
        <video controls autoPlay>
          <source src={props.playing.source} type='video/mp4' />
        </video>
        <div className='Player-back'>
          <button type='button' onClick={() => props.history.goBack()}>
            Regresar
          </button>
        </div>
      </div>
    ) : <Redirect to='/404' />;
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

const mapDispatchToProps = {
  getVideoSource,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);
</code>

En este bloque el profesor puso original en lugar de originals por si tienen el mismo problema que yo.

case 'GET_VIDEO_SOURCE':
      return {
        ...state,
        playing: state.trends.find((item) => item.id === Number(action.payload)) || state.originals.find((item) => item.id === Number(action.payload)) || [],
      };```

Que tal!
Les comparto el initialState actualizado con nuevos sources para los videos, ya que los que ven铆an originalmente no se pod铆an acceder.

https://gist.github.com/Fredyspt/087cc10ad1ac0513eb2683cb684b5a15

馃 Ac谩 el c贸digo trabajado hasta el momento: https://github.com/AndresCampuzano/React-Router-and-Redux/commits/master

Video sin copy right:

http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4

馃槈

 const handlePlay = (idPlayer)=>{
        props.getVideosSource(idPlayer)
    }

 <Link to={`/player/${id}`}>
                    <img 
                        className="carousel-item__details--img" 
                        src={playIcon} 
                        alt="" 
                        onClick={()=>handlePlay(id)}
                    />
                </Link>
 const [ loading, setLoading ] = useState(true);

loading ? null :hasPlaying?( ...
const Player = (props) => {
    const { id } = props.match.params;
    const hasPlaying = Object.keys(props.playing).length>0
    const [ loading, setLoading ] = useState(true);
    useEffect(()=>{
        console.log(loading)
        setLoading(false)
        props.getVideosSource(id)
    },[])
    return loading ? null :hasPlaying?(
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={()=>props.history.goBack()}>
                    Regresar
                </button>
            </div>
        </div>
    ):<Redirect to="/404" />
}

20 Enero 2022 React Router Dom v6

El uso de esto ya no funciona

const { id } = props.match.params;
props.history.goBack()

Por lo cual se debe utilizar lo siguiente para capturar los params

import { useParams } from "react-router-dom";

const { id } = useParams();

Y para volver atras

import { useNavigate } from "react-router-dom";

const navigate = useNavigate();

<button type="button" onClick={() => navigate("../")}>
	Regresar
</button>

Hola!
Las url de los videos ya no est谩n disponibles 馃槮

otra forma de hacer la l贸gica del reducer:

playing: [...state.trends, ...state.originals].find(x => x.id == Number(action.payload))

Hola! por si a alguno le comienza a dar error 401 Unauth al momento de darle play al video, me parece que es debido a que los videos han dejado de ser publicos (Diciembre 2020) y se requiere un TOKEN para accesar a ellos.

Una alternativa es buscar mas sample videos en internet y reemplazar el source de los items en nuestra API. Pueden revisar estos que estan alojados en google-api: Public videos gist

Si en alg煤n momento no se les reproduce el video, es por falta de permisos, para obtenerlos, lo que hice fue buscar una biblioteca de videos gratuita y actualizar las rutas. esta es una de las que mire y actualice.

https://player.vimeo.com/external/444828749.sd.mp4?s=497e66c950ceb28b638ee115a172b70706399cb3&profile_id=165&oauth2_token_id=57447761

Me queda la duda, esperando algui茅n haya aplicado. S铆 hago el front con lo aplicado en el curso, es necesario aplicar router y redirect usando el backend de django?

/* eslint-disable jsx-a11y/heading-has-content */
/* eslint-disable no-nested-ternary */
/* eslint-disable func-call-spacing */
/* eslint-disable react/destructuring-assignment */
/* eslint-disable react/prop-types */
/* eslint-disable jsx-a11y/media-has-caption */
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';

import { getVideoSuorce } from '../actions';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
  const { id } = props.match.params;
  const [loading, changeLoading] = useState(true);
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSuorce(id);
    changeLoading(false);
  }, []);

  return loading
    ? <h1 />
    : hasPlaying ? (
      <div className="Player">
        <video controls autoPlay>
          <source src={props.playing.source} type="video/mp4" />
      Your browser does not support HTML5 video.
        </video>
        <div className="Player-back">
          <button type="button" onClick={() => props.history.goBack()}>
                Regresar
          </button>
        </div>
      </div>
    ) : <Redirect to="/404/" />;
};

const mapStateToProps = (state) => ({
  playing: state.playing,
});

const mapDispatchToProps = {
  getVideoSuorce,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);

Lo que hice fue a帽adir un estado para determinar cuando esta cargando y mientras carga regreso un div del tama帽o del viewport para evitar mostrar el header

const [loading, setLoading] = useState(true);
useEffect(() => {
    props.getVideoSource(id);
    setLoading(false);
  }, []);
  if (loading) return <div style={{ height: '100vh', width: '100vw' }} />;
  return hasPlaying ? (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : <Redirect to='/404/' />;

La idea que tengo es simplemente revisar si esta cargando el elemento

const [loading, changeLoading] = useState(true);
const { id } = props.match.params;
const hasPlaying = Object.keys(props.playing).length > 0;

useEffect(() => {
    props.getVideoSource(id);
    changeLoading(false);
}), [];

if(loading) return <div>Cargando...</div>

return hasPlaying ? (
    <div className="Player">
        <video controls autoPlay>
            <source src={props.playing.source} type="video/mp4" />
        </video>
        <div className="Player-back">
            <button type="button" onClick={() => props.history.goBack()}>Regresar</button>
        </div>
    </div>
) : <Redirect to="/404/" />

Solo a帽adi un setTimeOut al componente Redirect para que sea lo ultimo en cargar en caso de que hasPlaying demore.

return hasPlaying ? ( 
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => {props.history.goBack()}}>
                    Regresar
                </button>
            </div>
        </div>
     ) : (
        setTimeout(() => {
            <Redirect to="/404/" />   
        },0)
     )

Me resulta SUPER CONFUSO que llame al m茅todo getVideoSource() cuando en realidad es un setter, porque ese m茅todo lo que est谩 haciendo es setear el estado de playing en la 鈥渂ase de datos鈥 que se est谩 simulando.

Hola. Pues mi soluci贸n fue intentar algo con Async await, no se si sea la mejor soluci贸n. Al hacerlo me top茅 con un problema en la configuraci贸n del Babel ya que necesit茅 incluir el plugin de @babel/plugin-transform-runtime para poder trabajar con asincronismo.

useEffect(() => {
    let didCancel = false;
    (async () => {
      !didCancel && setloading(true)
      try {
        await props.getVideoSource(id);
        !didCancel && sethasPlaying(true);
      } catch (err) {
        sethasPlaying(false);
      } finally {
        !didCancel && setloading(false);
      }
    })();
    return () => { didCancel = true; }
  }, []);

De esa manera agregu茅 un estado de loading. 驴Qu茅 opinan?

En lo personal pienso que cuando ya esto se realiza en la practica la mayoria son llamadas a APIs por lo que creo que lo mejor es manejar las promesas y async await. En lo personal es mejor implementar un Loading鈥 mientras nuestros datos son obtenidos.

como playing:{} hace parte del estado global, siempre va a tener algo adentro menos la primera vez que se monte el componente Player y se ejecute props.getVideoSource(id), entonces hasPlaying siempre sera true despu茅s de que tenga un primer valor seteado y no funcionara para lo que queremos hacer.

lo que propongo para este reto es mapear los videos en el componente Player desde el estado y hacer el filtro directamente en el componente Player. De esta forma la variable video siempre tendr谩 el video o undefined, y nos servir谩 para la validaci贸n del render 馃馃:

const VideoPlayer = (props) => {
  const { id } = props.match.params;
  const { videos } = props;
  const video = videos.find((item) => item.id === Number(id));

  return video ? (
    <div className="Player">
      <video controls>
        <source src={video.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button type="button" onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <Redirect to="/404" />
  );
};

const mapStateToProps = (state) => ({
  playing: state.playing,
  videos: [...state.mylist, ...state.trends, ...state.originals],
});

export default connect(mapStateToProps, null)(VideoPlayer);

En el initialState el playing es un Array no un objeto

initialState = {
  "user": {},
  "playing": [],
  "myList": [],
  ...

No me arrancaba, simplemente no cargaba nada. La solicio贸n fu茅 cambiar

"playing":{} por "playing":[]

Yo voy haciendo el ejercicio junto con el profesor, pero no lo voy haciendo igual.
Yo quise a帽adirle interactividad a mi c贸digo conect谩ndome a una API, y as铆 pude notar la diferencia entre dos tipos de actions:

  1. Traslado del payload puro, sin ningun tipo de tratamiento (como un fetch) as铆ncrono de por medio,
  2. Con un tratamiento de por medio, del payload.

El payload es el argumento que pasas a la funci贸n Action.

En la forma 1, usas esta forma:

const loginRequest = (payload) => ({
  type: 'LOGIN_REQUEST',
  payload,
});

export default loginRequest;

Simplemente el payload que pasas de argumento, se lo pasas al Reducer. No hay m谩s.



En la forma 2, tienes que hacer algo para transformar el payload original en algo m谩s:

import axios from 'axios';
import { moviePath } from '../utils/Vars';

const getVideoSource = (payload) => async (dispatch) => {
  const response = await axios.get(`${moviePath}${payload}/videos?api_key=${process.env.APIKey}&language=en-US`);

  dispatch({
    type: 'LOAD_SOURCE_VIDEO',
    payload: {
      id: payload,
      key: response.data.results.find((item) => item.site === 'YouTube'),
    },
  });
};

export default getVideoSource;

En este c贸digo recibo el Payload (un ID de una pel铆cula) y hago un fetch a la API para traer la url de Youtube, y pasarlo al reproductor (REDUCER).
En este caso el Dispatch, es el middleware de Redux-thunk.
Esa es la forma de manejar el asincronismo con redux.

Para los interesados en usar una pantalla de carga, les comparto este recurso donde pueden encontrar loaders hechos con puro CSS: https://loading.io/css/

pr谩ctica:

Da error.

En vez de mandar la informaci贸n en el componente Player鈥o mande la informacion al objeto playing desde CarouselItem; usando onClick en el boton de PlayIcon se dispara una funcion la cual actualiza nuestro objeto playing.

import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; 
import { setFavorite, deleteFavorite, sendVideoSource } 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, language, isMyList } = props;

  const handleSetFavorite = () => {
    props.setFavorite({
      id, cover, title, year, contentRating, duration, language,
    });
  };

  const handleDeleteFavorite = (itemId) => {
    props.deleteFavorite(itemId);
  };

  const handlePlayIcon = (id) => {
    props.sendVideoSource(id);
  };

  return (
    <div className="carousel-item">
      <img className="carousel-item__img" src={cover} alt={title} />
      <div className="carousel-item__details">
        <div>
          <Link to={`/player/${id}`}>
            <img
              className="carousel-item__details--img"
              src={playIcon}
              alt="Play Icon"
              onClick={() => handlePlayIcon(id)}
            />
          </Link>
          {
            isMyList ?
              <img
                className="carousel-item__details--img"
                src={removeIcon}
                alt="Remove Icon"
                onClick={() => handleDeleteFavorite(id)}
              />
            :
              <img
                className="carousel-item__details--img"
                src={plusIcon}
                alt="Plus Icon"
                onClick={handleSetFavorite}
              />
          }
        </div>
        <p className="carousel-item__details--title">{title}</p>
        <p className="carousel-item__details--subtitle">{`${year} ${contentRating} ${duration}`}</p>
        <p className="carousel-item__details--language">{`${language}`}</p>
      </div>
    </div>
  );
};

CarouselItem.propTypes = {
  cover: PropTypes.string,
  title: PropTypes.string,
  year: PropTypes.number,
  contentRating: PropTypes.string,
  duration: PropTypes.number,
  language: PropTypes.string,
};

const mapDispatchToProps = {
  setFavorite,
  deleteFavorite,
  sendVideoSource,  
};

export default connect(null, mapDispatchToProps)(CarouselItem);

Y en el componente Player solo traje la informacion del objeto playing鈥odiendo usar el <Redirect to="/404/" /> en dado caso que se escriba la url directamente en el navegador. EJEMPLO: /player/78
no existe el id 78 por lo cual nos mandara al /404/

import React from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
  const { playing } = props;
  const hasPlaying = Object.keys(playing).length > 0;

  return hasPlaying ? (
    <div className="Player">
      <video controls autoPlay>
        <source src={playing.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button type="button" onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : <Redirect to="/404/" />;
};

// para obtener el objeto "playing"
const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

export default connect(mapStateToProps, null)(Player);
class Player extends React.Component {
  componentDidMount() {
    const { id } = this.props.match.params;
    this.props.getVideoSource(id);
  }

  render() {
    const hasPlaying = Object.keys(this.props.playing).length > 0;
    return hasPlaying ? (
      <div className="Player">
        <video controls autoPlay>
          <source src={this.props.playing.source} type="video/mp4" />
        </video>
        <div className="Player-back">
          <button type="button" onClick={() => this.props.history.goBack()}>
            regresar
          </button>
        </div>
      </div>
    ) : (
      <NotFound />
    );
  }
}
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getVideoSource } from "../actions";
import "../assets/styles/components/Player.scss";

const Player = props => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div className="Player">
      <video controls autoPlay>
        <source src={props.playing.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button type="button" onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <h1>Loading ... </h1>
  );
};

const mapStateToProps = state => {
  return {
    playing: state.playing
  };
};

const mapDispatchToProps = {
  getVideoSource
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Player);

bueno esta clase me costo, e igual la vi como 5 veces jejejej

Ami esto fue lo que me funciono

import React, {useEffect}  from 'react';
import '../assets/styles/components/Player.scss';
/* importamos connect redux */
import { connect } from 'react-redux';
import { getVideoSource } from '../actions';
import NotFound from '../containers/NotFound';


const Player = props => {
  const { id } = props.match.params;
  const { playing=[] } = props;

const hasPlaying = Object.keys(playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ?  (
    <div className="Player">
        <video controls autoPlay>
          <source src={props.playing.source} type="video/mp4" />
        </video>
         <div className="Player-back">
           <button type= "button" onClick={() => {props.history.goBack();}}>
             Regresar
           </button>
         </div>
    </div>
  ) : <NotFound />
};
/**  creamos la function mapstatetoprops*/
const mapStateToProps = state => {
  return {
    playing: state.playing
  };
};

const mapDispacthToProps = {
  getVideoSource
};
/** conectamos nuestro componente con redux */
export default connect(mapStateToProps,mapDispacthToProps)(Player);

驴C贸mo se podr铆a usar el find o alg煤n otro m茅todo para filtrar elementos en un array de dos dimensiones? 馃槳
.

// Mi state se ve algo as铆
const initialState = {
	categories: [
		{
			title: "Trends",
			videos: [
				//... videos
			]
		},
		{
			title: "Platzi originals",
			videos: [
				// ...videos
			]
		}
		....
	]
}

Esta es mi propuesta, mientras hasPlaying = True, renderiza el video, sino env铆a una etiqueta <h1>

const Player = props => {
    const { id } = props.match.params;
    const hasPlaying = Object.keys(props.playing).length > 0;

    useEffect(() => {
        props.getVideoSource(id);
    }, []);

    return hasPlaying ?  (
        <div className="Player">
            <video controls autoPlay>
                <source src="" type="video/mp4" />  
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => props.history.goBack() }>
                    Regresar
                </button>
            </div>
        </div>
    ): <h1>Descargando recurso...</h1>;
};

como en el CarouselItem tenemos la informacion de cada elemento de las listas, cuando se realiza el click sobre player, en lugar de tener un link podria tener un onClick, que cargue la informaci贸n en el state.playing y luego redireccione a Player usado props.history, de esa forma al montar el componente player ya en el state esta ese valor y nos ahorramos las busquedas en las listas.

mi propuesta es:
en initialState definir playing como null
en reducers declarar una variable const error = new Error()
en lugar de devolver [] si no encuentra el video source, devolver error
en el player declarar variable const isPlayingError = playing instanceof Error
adaptar la vista de Player:

const isPlayingError = playing instanceof Error;
    return playing 
    ? isPlayingError 
        ? <Redirect to="/404" />
        :(
            <div className="Player">
                <video controls autoPlay>
                    <source src={playing.source} type="video/mp4" />
                </video>
                <div className="Player-back">
                    <button type="button" onClick={() => history.goBack()}>Regresar</button>
                </div>
            </div>
        )
    : (
      <h1>Buscando origen del video ...</h1>  
    );

para resolver el reto puedes cambiar el orden: return !hasPlaying ? <NotFound /> : ( y ya todo el codigo

      <div className="Player">
         <video controls autoPlay>
             <source src={props.playing.source}  type="video/mp4" />
         </video>
         <div className="Player-back">
           <button type="button" onClick={() => props.history.goBack()}>
               Regresar
           </button>
         </div>
      </div>
    ): [];
};

Esta fue mi soluci贸n y funciono, Mas no se si esta bien
 

Yo lo hice de esta manera, y lo hice un poco diferente a como lo hizo el profesor Oscar

import React from 'react';
import '../assets/styles/components/Player.scss';
import { connect } from 'react-redux';
import NotFound from './NotFound';

const Player = (props) => {
  const { id } = props?.match?.params;
  const video =
    props?.trends.find((trend) => trend.id.toString() === id.toString()) ||
    props?.originals.find((original) => original.id.toString() === id);
  console.log(video);

  const handleGoBack = () => {
    props.history.goBack();
  };

  if (!video) return <NotFound />;

  return (
    <div className="player">
      <video controls autoPlay>
        <source src={video.source} type="video/mp4" />
      </video>
      <div className="player-back">
        <button type="button" onClick={handleGoBack}>
          Regresar
        </button>
      </div>
    </div>
  );
};
const mapStateToProps = (state) => {
  return { trends: state.trends, originals: state.originals };
};
export default connect(mapStateToProps)(Player);

yo utilice redirect y le puse un setTimeout

  return hasPlaying ? (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : setTimeout(() => {
    <Redirect to='/404/' />;
  }, 0);
};

Tengo una duda! El action.payload 驴Que es en nuestro archivo de Player? Que propiedad es, en que se transforma, no se si me doy a entender

const Player = props => {

    const { id } = props.match.params;
    const [ loading, setLoading ] = useState(true);
    const hasPlaying = Object.keys(props.playing).length > 0;
    

    useEffect(() => {
        props.getVideoSource(id);
        setLoading(false);
    }, []); 

    if (loading) return <h2>Cargando video...</h2>

    return hasPlaying ? (
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => props.history.goBack()}>
                    Regresar
                </button>
            </div>
        </div>
    )
    :
    <Redirect to='/404' />
};

const mapStateToProps = state => {
    return {
        playing: state.playing,
    }
};

const mapDispatchToProps = {
    getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player);

Es normal no entender nada hasta este punto del curso? 馃槮

Para resolver lo que se indic贸 en el v铆deo, hice lo siguiente

  return hasPlaying && (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  )

Le agregue algo m谩s para poder tener en cuenta si es que el id del v铆deo no existe, as铆 qued贸 mi Player.jsx

/* eslint-disable react/destructuring-assignment */
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { getVideoSource } from '../actions'
import '../assets/styles/components/Player.scss'
import NotFound from './NotFound'

const Player = (props) => {
  // Esto lo manda router al momento de generar la ruta que es en player/:id <- Eso
  const { id } = props.match.params
  const hasPlaying = Object.keys(props.playing).length > 0
  const result = props.playing

  useEffect(() => {
    props.getVideoSource(id)
  }, [])

  if (result.length === 0) {
    return <NotFound />
  }

  return hasPlaying && (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  )
}

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  }
}

const mapDispatchToProps = {
  getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player)

Les dejo mi action鈥

    case 'GET_VIDEO_SOURCE':
      const { trends, originals } = state;
      return {
        ...state,
        playing: trends.concat(originals).find(item => item.id === Number(action.payload))
      }```

Lo que hice fue encapsular el redirect en una arrow function para que no lo ejecutara autom谩ticamente:

: () => <Redirect to="/404/" />

Look my code!

Esta es mi soluci贸n:

const  hasPlaying = async () => {
    try {
        const response = await Object.keys(props.playing).length > 0
        return response
    }
    catch(error) {
        console.error(error)
    }
}
import React, { useLayoutEffect } from 'react';
import { connect } from 'react-redux';
import { getVideoSource } from '../actions';
import '../assets/styles/components/Player.scss';
import NotFound from './NotFound';

const Player = (props) => {
  {}
  // const { id } = props.match.params;
  const { playing, match: { params: { id } } } = props;
  const hasPlaying = Object.keys(playing).length > 0;

  useLayoutEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div className='Player'>
      <video controls autoPlay>
        <source src={playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : <NotFound />;
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};
const mapDispatchToProps = {
  getVideoSource,
};
export default connect(mapStateToProps, mapDispatchToProps)(Player);```
export const PlayerScreen = () => {

    const { trends, originals, playing } = useSelector(state => state.movies)
    const { goBack } = useHistory();
    const { id } = useParams();
    const dispatch = useDispatch()

    useEffect(() => {
        dispatch(moviesVideoPlayingAction(id))
    }, [trends, originals])

    return playing ? (
        <div className="Player">
            {
                playing &&
                <video controls autoPlay>
                    <source src={playing.source} type="video/mp4" />
                </video>
            }
            <div
                className="Player-back"
                onClick={() => goBack()}
            >
                <button type="button">
                    Regresar
                </button>
            </div>
        </div>
    ) : (
        <NotFoundScreen />
    )
}

Lo que he hecho ha sido cambiar la comprobaci贸n de

const hasPlaying = Object.keys(....

por una funci贸n que retorna una promesa, de esa manera si la condici贸n se cumple resuelve con true y si no con false.

const hasPlaying = () => {
	return new Promise((resolve, reject) => {
		if (Object.keys(....)){
			resolve(true)
		} else {
			reject()
		}
	});
};

Espero que esto os ayude, aun as铆 la lista de favoritos no me funciona con este m茅todo al igual que con el de Oscar a pesar de haberlo a帽adido a la l贸gica de mi reducer.

No se si fu铆 al 煤nico que se le dificulto un poco entender esta clase, quisiera compartir una explicaci贸n que me hice a mi mismo para entenderlo mejor, tal vez le pueda ayudar a alguien. 馃槃

Para entenderlo vamos a analizarlo paso a paso.

Estamos enviando una ruta din谩mica, que var铆a seg煤n el id

Esto hace, que cuando demos click nos mande a dicha ruta, ahora necesito configurar este comportamiento, para que en esa ruta se configure mi source como quiero.

  1. Primero voy a hacer match con ese Id para recibirlo en mi player
  1. Luego conectar茅 mi app al store para traer mi playing y poder incluso modificarlo cuando reciba el id.
  1. Una vez conectada vamos a configurar el action y el reducer para poder: seg煤n el id que reciba devolver el video correspondiente y as铆 poder tomar mi source del video.

Aqu铆 b谩sicamente tomo mis trends y mis originals y busco en ellos el item el cual coinsida con el id que le pas茅 al payload, adem谩s tengo que convertirlo en n煤mero ya que lo recibo como string.

  1. Ya que tengo mi store configurado voy a traer ciclodevida a mi componente para que cuando se monte el elemento le pasemos el id al action y este pueda traer el item que itera para usar su source.
  1. Echo esto voy a validar si hay un item reproduciendose, si es el paso paso mi componente con el source.
  1. Por 煤ltimo ya que esto me daba un bugg que me mandaba a 404 voy a configurar un estado que valide el loading

Hay muy buenas soluciones dadas por los compa帽eros en comentarios anterioes.
yo utilice el useLayoutEffect.

import React, { useLayoutEffect } from 'react';

  useLayoutEffect(() => {
    props.getVideoSource(id);
  }, []);

<NotFound />

La funcionalidad del video player se puede tambi茅n usando solo el estado global, aqui esta la otra forma. (Ambas formas son validas)

import React from 'react';
import { connect } from 'react-redux';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
    const { videos, history } = props;
    const { id } = props.match.params;
    let video = videos.find(el => el.id == id);
    return (
        <div className="Player">
            {video && <video autoPlay>
                <source src={video.source} media="video/mp4" />
            </video>}
            <div className="Player-back">
                <button type="button" onClick={() => history.goBack()}>Regresar</button>
            </div>
        </div>
    );
};

const mapStateToProp = state => {
    return {
        videos: [...state.trends, ...state.originals]
    };
}

export default connect(mapStateToProp, null)(Player);

A quien pueda interesar鈥 leyendo los comentarios me di cuenta que ya los links de los videos no funcionan, entonces cambi茅 algunos de los links (que si de 2 o 3 primeros en trends) por unos que si sirvieran a modo de tener videos que carguen y reproduzcan y videos que no鈥
Luego se me ocurri贸 este c贸digo usando 鈥渙nerror鈥 a la hora de cargar el video a modo de que si el link funciona, bien y reproduce; caso contrario, nos vamos al 404:
IMPORTANTE, en index.js, debes colocar la variable 鈥渧alidator鈥 en el initial state:
const initialState = {
鈥渦ser鈥: {},
鈥減laying鈥: {},
鈥渕ylist鈥: [],
鈥渧alidator鈥:0,
鈥 todo lo dem谩s鈥

En Player.jsx :
import React,{useState,useLayoutEffect} from 'react鈥
import {Link} from 'react-router-dom鈥
import '鈥/assets/styles/components/Player.scss鈥
import {connect} from 'react-redux鈥
import {getVideoSource} from '鈥/actions鈥
import NotFound from '鈥/containers/NotFound鈥
import {Redirect} from 鈥榬eact-router-dom鈥

const Player= props =>{
const { id } = props.match.params;
const hasPlaying = Object.keys(props.playing).length >0

const [validator,setValidator]=useState(props.validator)

useLayoutEffect(()=>{
    props.getVideoSource(id);
},[])

const handler= (event)=>{
    setValidator(Object.keys(event).length)
}


return hasPlaying? (
    <div className = "Player">
        <video id='video' controls autoPlay onError={handler}>
            <source src={props.playing.source} type="video/mp4"/>
        </video>
        <div className="Player-back">
            <Link to ='/'>
                <button type="button">
                    Regresar
                </button>
            </Link>
            
        </div>
        {
            validator>0?
            <Redirect to='/404/' />:
            console.log('allGood')
        }
    </div>
): <NotFound/>

};

const mapStateToProps =(state)=>{
return{
playing:state.playing,
validator: state.validator
}
}

const mapDispatchToProps = {
getVideoSource,
}

export default connect(mapStateToProps,mapDispatchToProps)(Player)

Mi soluci贸n fue simular un 鈥渃argando鈥︹ ya que en la primera corrida, el estado no contiene nada en playing, por lo tanto se debe capturar.

Les pongo en el c贸digo un setTimeout para tener un poco de visibilidad sobre lo que realmente esta pasando:

import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { getVideoSource } from "../actions";
import "../assets/styles/components/Player.scss";

const TEST_MP4 =
  "enlace_a_un_video.mp4";

const Player = (props) => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setTimeout(() => {
      props.getVideoSource(id);
      setIsLoading(false);
    }, 2000);
  }, []);

  return isLoading ? (
    <h3>Cargando {JSON.stringify(props.playing)}</h3>
  ) : hasPlaying ? (
    <div className="player">
      <video controls autoPlay>
        {/* <source src={props.playing.source} type="video/mp4" /> */}
        <source src={TEST_MP4} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button
          type="button"
          onClick={() => {
            props.history.goBack();
          }}
        >
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <Redirect to="/404" />
  );
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

const mapDispatchToProps = {
  getVideoSource,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);

no me quedo claro por que es que se muestra el notFound y luego se carga el video鈥

Mi soluci贸n:
Player.jsx

import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getVideoSource } from '../actions';
import NotFound from '../components/NotFound';
import '../assets/styles/containers/Player.scss';

const Player = (props) => {
  const { playing, match } = props;
  const { id } = match.params;

  const hasPlaying = Object.keys(playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div>
      <div className='Player'>
        <video controls={true}>
          <source src={playing.source} type='video/mp4' />
        </video>
      </div>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <NotFound />
  );
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

const mapDispatchToProps = {
  getVideoSource,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);

reducers.js

    case GET_VIDEO_SOURCE:
      return {
        ...state,
        playing: state.trends.find((item) => item.id === Number(action.payload)) ||
                 state.originals.find((item) => item.id === Number(action.payload)) ||
                 [],
      };

Me da error 401, por falta de credenciales, para cargar la ruta del video

La verdad siempre he tenido dudas con los actions y los reducers, algo me dice que s贸lo deben utilizarse si es necesario alterar el estado global.

En este caso, lo hice sin necesidad de tener playing en el store y traje la informaci贸n del video directamente desde el estado.

import React from "react";
import { isEmpty } from "lodash";
import { useSelector } from "react-redux";
import { Redirect, useHistory, useParams } from "react-router-dom";

import "../assets/styles/components/Player.scss";

export default function Player() {
  const history = useHistory();
  const { id } = useParams();

  const playing = useSelector(
    (state) =>
      state.trends.find((item) => item.id === Number(id)) ||
      state.originals.find((item) => item.id === Number(id)) ||
      {}
  );

  if (!isEmpty(playing)) {
    return (
      <div className="Player">
        <video controls autoPlay>
          <source src={playing.source} type="video/mp4" />
        </video>
        <div className="Player-back">
          <button onClick={() => history.goBack()}>Regresar</button>
        </div>
      </div>
    );
  } else {
    return <Redirect to="/404" />;
  }
const Player = props => {

    const { id } = props.match.params;

    const hasPlaying = Object.keys(props.playing).length > 0;

    useEffect(() => {
        props.getVideoSource(id);
    }, [])

    if (!props.playing) return <h3>cargando...</h3>

    return hasPlaying ? (
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => props.history.goBack()}>
                    Regresar
                </button>
            </div>
        </div>
    ) : <Redirect to='/404' />;
}



const mapStateToProps = state => {
    return {
        playing: state.playing
    }
}

const mapDispatchToProps = {
    getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player);

a

n

No puedo reproducir el video se queda en negro y en la consola me muestra un error Failed to load resource: net::ERR_NAME_NOT_RESOLVED

Me he dado cuenta que para enviar y recibir el estado por medio del connect es mas tardio que el render tambien aportando que el uso de useEffect es asincrono asi que creo que ahi otra opcion para realizar un <Link/> donde no se muestre el url del video sin la necesidad de usar un playing en nuestra store , y la unica forma para que el usuario entre a ver este video en el player es haciendo click en el video donde.
1.modificariamos la ruta <Route exact path="/player/:id" component={Player}/> a <Route exact path="/player/" component={Player}/> (logrando de que la unica forma para que entre el usuario al player del video sea el interactuando con la pagina y no por medios de links)
2.luego iremos donde tenemos el link y digitamos en 鈥渢o鈥 lo siguiente: to={{pathname:/player,props:{url:url}}}
3.y listo nuestro player ya tiene capturado el url del video lo unico que falta es ir al <source/> del <video/> y su 鈥渟rc鈥 sera =this.props.location.props.url(this.props son los props del constructor)

// lo que debemos colocar en el archivo donde tenemos nuestras rutas
<Route exact path="/player/" component={Player}/>
//en el archivo donde tenemos el video 
 <Link 
                    to={{
                            pathname:`/player`,
                            props:{
                                url:url
                            }
                        }}
 >
// y en el archivo del player 
constructor(props){
this.props=props
}
render(){
	<video className="player_video" controls autoPlay>
          	<source src={this.props.location.props.url} type="video/mp4"/>
         </video>
}

si alguien tiene un aporte o un fallo en mi aporte les agradeceria si me lo escriben