A煤n no tienes acceso a esta clase

Crea una cuenta y contin煤a viendo este curso

Debug con Redux Devtools

28/29
Recursos

Redux Dev Tools nos va a servir mucho para entender mejor el flujo de nuestra informaci贸n en nuestra aplicaci贸n y poder realizar debugging de manera sencilla.

Solamente necesitas instalar la extensi贸n seg煤n el navegador que tengas:

Una vez instalado dentro de nuestro index.js vamos a a帽adir el siguiente c贸digo:

// importamos compose  
import { createStore, compose } from 鈥榬edux鈥;  
...  
  
  
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose  
  
const store = createStore(reducer, initialState, composeEnhancers  

Aportes 89

Preguntas 3

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesi贸n.

Leyendo la documentaci贸n, yo s贸lo agregu茅 lo siguiente y me funcion贸 a la perfecci贸n.

const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

const store = createStore(rootReducer, initialState, composeEnhancers)

Link de la documentaci贸n oficial: https://github.com/zalmoxisus/redux-devtools-extension#installation

Mi soluci贸n:

En el JSON de initialState en index.js:

"searchResult": [],

En el componente Search:

import React from 'react';
import classNames from 'classnames';
import { getVideoSearch } from '../actions';
import { connect } from 'react-redux';
import '../assets/styles/components/Search.scss';


const Search = (props) => {

    const { isHome, getVideoSearch } = props;

    const inputStyle = classNames('input', {
        isHome
    });

    const handleInput = (event) => {
        getVideoSearch(event.target.value);       
    }

    return (
        <section className="main">
            <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>

            <input 
                className={inputStyle} 
                type="text" 
                placeholder="Buscar..."
                onKeyUp={handleInput}
            />

        </section>
    )
};

const mapStateToProps = state => {
    return {
        searchResult: state.searchResult,
    }
}

const mapDispatchToProps = {
    getVideoSearch,
}


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

Se crea una accion en index.js en la carpeta action:

export const getVideoSearch = payload => ({
    type: 'GET_VIDEO_SEARCH',
    payload,
});

Se crea la funci贸n en un caso en el reducer en index.js:

 case 'GET_VIDEO_SEARCH':
            
            if(action.payload === "") return { ...state, searchResult: []};

            const listas = [...state.trends, ...state.originals];

            return {
                ...state,
                searchResult: listas.filter(item => item.title.toLowerCase().includes(action.payload.toLowerCase()))
            };

Dentro del Home, utilizamos la l贸gica de estas funciones para crear una secci贸n de b煤squeda:

import React from 'react';
import {connect} from 'react-redux';
import Header from '../components/Header';
import Search from '../components/Search';
import Categories from '../components/Categories';
import Carousel from '../components/Carousel';
import CarouselItem from '../components/CarouselItem';
import '../assets/styles/App.scss';

const Home = ({ myList, trends, originals, searchResult }) => {

    return (
        <>
            <Header />          
            <Search isHome />

            { 
                Object.keys(searchResult).length > 0 && 
                    (
                        <Categories title="Resultados de la busqueda...">
                            <Carousel>
                                {searchResult.map(item =>
                                    <CarouselItem 
                                    key={item.id} 
                                    {...item}
                                    />
                                )}
                            </Carousel>
                        </Categories>
                    )                        
            }

const mapStateToProps = state => {
    return {
        myList: state.myList,
        trends: state.trends,
        originals: state.originals,
        searchResult: state.searchResult,
    };
};

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

Reto solucionado:

  • Dentro del JSON que se encuentra en index.js puse un array con todo el contenido: content.
  • Gener茅 un action con el nombre searchVideo y un reducer con SEARCH_VIDEO.
  • Dentro del reducer hice (la b煤squeda se hace en min煤sculas para hacerlo insensitive case):
if (action.payload === '') {
        return {
          ...state,
          search: [],
        };
      }
      return {
        ...state,
        search: state.content.filter((items) => items.title.toLowerCase().includes(action.payload.toLowerCase())) ||
          [],
      };
  • En Search a帽ad铆 los componentes de Categories, Carousel, CarouselItem para hacer el render de los resultados.
  • En Search vincule la acci贸n para obtener los resultados
  • Hice un handleInput para capturar el valor del input cada que hay un cambio en el valor.

Dejo mi repositorio para mejor referencia:
https://github.com/manuelojeda/escuela-js-platzi-video/tree/feature/router-redux

Esta clase se debi贸 impartir posterior a la instalaci贸n y uso inicial de Redux, se hubiera entendido mejor la explicaci贸n.

import React from "react";
import { connect } from "react-redux";
import { searchVideo } from "../actions/";
import classNames from "classnames";
import "../assets/styles/components/Search.scss";

const Search = props => {
  const { isHome, searchVideo } = props;
  const inputStyle = classNames("input", {
    isHome
  });

  const handleInput = () => {
    searchVideo(event.target.value);
  };

  return (
    <section className="main">
      <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
      <input
        type="text"
        className={inputStyle}
        onChange={handleInput}
        placeholder="Buscar..."
      />
    </section>
  );
};

const mapDispatchToProps = { searchVideo };

export default connect(null, mapDispatchToProps)(Search);
case "SET_SEARCH":
      if (action.payload === "") return { ...state, search: [] };

      const listas = [...state.trends, ...state.originals];

      return {
        ...state,
        search: listas.filter(item =>
          item.title.toLowerCase().includes(action.payload)
        )
      };
export const searchVideo = payload => ({
  type: "SET_SEARCH",
  payload
});

import React from "react";
import { connect } from "react-redux";
import Search from "../components/Search";
import Categories from "../components/Categories";
import Carousel from "../components/Carousel";
import CarouselItem from "../components/CarouselItem";

import "../assets/styles/App.scss";

const Home = ({ myList, trends, originals, search }) => {
  return (
    <div className="App">
      <Search isHome />
      {search.length > 0 && (
        <Categories title="Search">
          <Carousel>
            {search.map(item => (
              <CarouselItem key={item.id} {...item} />
            ))}
          </Carousel>
        </Categories>
      )}
      {myList.length > 0 && (
        <Categories title="Mi Lista">
          <Carousel>
            {myList.map(item => (
              <CarouselItem key={item.id} {...item} isList={true} />
            ))}
          </Carousel>
        </Categories>
      )}
      <Categories title="Tendencias">
        <Carousel>
          {trends.map(item => (
            <CarouselItem key={item.id} {...item} />
          ))}
        </Carousel>
      </Categories>
      <Categories title="Originales de Platzi Video">
        <Carousel>
          {originals.map(item => (
            <CarouselItem key={item.id} {...item} />
          ))}
        </Carousel>
      </Categories>
    </div>
  );
};

const mapStateToProps = state => {
  return {
    myList: state.myList,
    trends: state.trends,
    originals: state.originals,
    search: state.search
  };
};

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

Buscador usando expresiones regulares

Yo us茅 expresiones regulares para obtener una lista de resultados que coincidan con el texto ingresado en el buscador

Cre茅 un nuevo key en el estado de redux llamado searchText inicializado con un string vac铆o y un action que lo modifica, ese action lo apliqu茅 en el input para que con cada cambio en el input cambie el estado en Redux as铆:
action

export const setSearchText = (payload) => ({
  type: 'SET_SEARCH_TEXT',
  payload,
});

reducer

case 'SET_SEARCH_TEXT':
      return {
        ...state,
        searchText: action.payload,
      };

Search.jsx

import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { setSearchText } from '../actions';
import '../assets/styles/components/Search.scss';

const Search = ({ isHome, setSearchText }) => {
  const inputStyle = classNames('input', {
    isHome,
  });

  const handleInput = (event) => {
    setSearchText(event.target.value);
  };

  return (
    <section className='main'>
      <h2 className='main__title'>驴Qu茅 quieres ver hoy?</h2>
      <input
        className={inputStyle}
        type='text'
        placeholder='buscar...'
        onChange={handleInput}
      />
    </section>
  );
};

const mapDispatchToProps = {
  setSearchText,
};

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

En el Home agregu茅 searchText al mapStateToProps y cre茅 una expresi贸n regular usando su valor en lower case.

const regex = new RegExp(`.*${searchText.toLowerCase()}.*`);

En el return del componente justo despu茅s del componente Search agregu茅 una validaci贸n para saber si tengo alg煤n texto en la barra de b煤squeda y si lo tengo muestro una nueva categor铆a con la lista de resultados filtrando trends y originals haciendo la comparaci贸n con la expresi贸n regular y el t铆tulo de las pel铆culas.

{
    searchText && (
        <Categories title='Resultados de la b煤squeda'>
        <Carousel>
            {
            trends.filter((item) => regex.test(item.title.toLowerCase()))
                .map((item) => (
                <CarouselItem key={item.id} {...item} />
                ))
            }
            {
            originals.filter((item) => regex.test(item.title.toLowerCase()))
                .map((item) => (
                <CarouselItem key={item.id} {...item} />
                ))
            }
        </Carousel>
        </Categories>
    )
}

Genial esta herramienta!

as铆 que el codigo del buscador

import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { getVideos, isSearching } from "../actions";

import classNames from "classnames";
import "../assets/styles/components/Search.scss";

const Search = props => {
  const { isHome } = props;
  const inputStyle = classNames("input", {
    isHome,
  });

  const handleChance = event => {
    if (event.target.value === "") {
      props.isSearching(false);
    } else {
      props.isSearching(true);
      props.getVideos(event.target.value);
    }
  };

  return (
    <section className="main">
      <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
      <input
        type="text"
        className={inputStyle}
        onChange={handleChance}
        placeholder="Buscar..."
      />
    </section>
  );
};

Search.prototype = {
  isHome: PropTypes.bool,
  getVideos: PropTypes.func,
};

const mapToDispatchToPros = {
  getVideos,
  isSearching,
};

export default connect(null, mapToDispatchToPros)(Search);

esto se agrego al Home, adem谩s de las variables necesarias del state, por sierto al state agregue 2 variables mas una para indicar si se esta realizando una busqueda y otra donde se guarda un arreglos con los resultados de la busqueda:

if (isSearching) {
    return searching.length > 0 ? (
      <>
        <Search isHome />
        <Categories title="B煤squeda">
          <Carousel>
            {searching.map(item => (
              <CarouselItem key={item.id} {...item} isMyList={true} />
            ))}
          </Carousel>
        </Categories>
      </>
    ) : (
      <VideoNotFound />
    );
  }

VideoNotFound es un componente donde solo doy la respuesta al no conseguir nada
se agregan estos 2 actions


export const getVideos = payload => ({
  type: "GET_VIDEOS",
  payload,
});

export const isSearching = payload => ({
  type: "IS_SEARCHING",
  payload,
});

y los reducers:


 case "IS_SEARCHING":
      return {
        ...state,
        isSearching: action.payload,
      };

      break;

    case "GET_VIDEOS":
      return {
        ...state,
        searching: state.trends
          .concat(state.originals)
          .filter(item =>
            item.title.toLowerCase().includes(action.payload.toLowerCase())
          ),
      };
      break;

El repositorio completo esta en:

https://github.com/RosoPenaranda/reactPractico

Reto: Use debounce para lanzar la b煤squeda solo despu茅s que el usuario terminara de tipear

Seach.jsx:

...
import { debounce } from "debounce";
import { searchVideo } from '../actions';

const Search = (props) => {
  const { isHome } = props;
  const inputStyle = className('input', {
    isHome
  });

  const inputhandler = () => {
    const value = document.querySelector('#seach-input').value;
    props.searchVideo(value);
  }

  return (
    <section className="main">
      <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
      <input
        id="seach-input"
        type="text"
        className={inputStyle}
        placeholder="Buscar..."
        onChange={debounce(inputhandler, 500)}
      />
    </section>
  )
};

const mapDispatchToProps = {
  searchVideo,
};

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

actions:

export const searchVideo = payload => ({
  type: 'SEARCH_VIDEO',
  payload
});

reducer:

    case 'SEARCH_VIDEO':
      if (action.payload === '') return {...state, results: []}

      let results = []
      const textSeach = action.payload.toLocaleLowerCase();
      const resultsTrends = state.trends.filter(item => item.title.toLowerCase().includes(textSeach))
      const resultsOriginals = state.originals.filter(item => item.title.toLowerCase().includes(textSeach))
      results = results.concat(resultsTrends);
      results = results.concat(resultsOriginals);

      return {
        ...state,
        results
      }
...

Reto terminado!

He de decir que me bas茅 en mayor medida en la soluci贸n de los compa帽eros, pero para eso est谩 la comunidad (Hermosa comunidad)! jejej

  1. Agregu茅 un nuevo arreglo en el store el cual contiene los buscados.
  1. Cre茅 una nueva secci贸n que ilustrar谩 los buscados.
  1. Cre茅 el action y reducer que se encarga de hacer las validaciones necesarias para filtrar los videos que quiere el usuario.

  2. Dentro del componente search, conect茅 el mensionado con el store, le pas茅 el action que recibe mis datos y cre茅 la funci贸n handleSearch que recibe los datos que digita el usuario y los manda como payload al action.

Gracias Oscar por este gran curso! 馃槃

Mi soluci贸n al reto:
1 - Agrego un array al initialState para guardar los resultados de la b煤squeda

'searchResult': {}

2 - En el componente Search agrego toda la l贸gica para recibir el string del input y mostrar los resultados.

import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { searchRequest } from '../actions';
import Categories from './Categories';
import Carrousel from './Carrousel';
import CarrouselItem from './CarrouselItem';
import '../assets/styles/components/Search.scss';

const Search = (props) => {
  const { searchResult, isHome } = props;
  const inputStyle = classNames('input', { isHome });

  const handleEnter = (event) => {
    if (event.key === 'Enter') {
      props.searchRequest(event.target.value);
    }
  };

  return (
    <section className='main'>
      <h2 className='main__title'>驴Qu茅 quieres ver hoy?</h2>
      <input
        name='search'
        className={inputStyle}
        type='text'
        placeholder='Buscar...'
        onKeyUp={handleEnter}
      />

      {searchResult.length > 0 && (
        <Categories title='Resultados'>
          <Carrousel>
            {searchResult.map((item) => (
              <CarrouselItem
                key={item.id}
                {...item}
              />
            ))}
          </Carrousel>
        </Categories>
      )}
    </section>
  );
};

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

const mapDispatchToProps = {
  searchRequest,
};

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

3 - En reducers/index.js agrego un case para hacer la b煤squeda y guardarlo en 鈥榮earchResult鈥

case 'SEARCH_REQUEST':
      return {
        ...state,
        searchResult: state.trends.filter((item) => item.title.toLowerCase().search(action.payload.toLowerCase()) !== -1) ||
        state.originals.filter((item) => item.title.toLowerCase().search(action.payload.toLowerCase()) !== -1) ||
        [],
      };

4 - En actions/index.js creamos el m茅todo 鈥榮earchRequest鈥

export const searchRequest = (payload) => ({
  type: 'SEARCH_REQUEST',
  payload,
});

Si alguno est谩 utilizando reduxthunk como middleware, para poder habilitar la opci贸n del compose se debe pasar la propiedad de la siguiente manera.

const store = createStore(
  reducer,
  initalState,
  composeEnhancers(applyMiddleware(reduxThunk))
);

Esta incre铆ble el redux dev tools aunque creo que hubiera sido mas 煤til verlo antes para no poder resolver los problemas durante el curso

隆Me encant贸 esta herramienta! 鉂わ笍

Hubiera sido muy bueno verla antes en el curso, me hubiera ahorrado varios quebraderos de cabeza y menos console.log() por todos lados. La sumamos al caj贸n de herramientas del Developer 馃槢

Lo solucion茅 basicamente as铆: mando todo el input del form como payload, en el initialstate a帽adi un nueva propiedad llamada 鈥渟earch鈥 que tiene como valor un objeto con las propiedades 鈥榚xist鈥 (para comprobar si hay algun resultado, esto lo hice porque tenia un bug que cuando borraba el input, los elementos iniciales no volvian a renderizar en el home)

<h3>Reducer:</h3>
case "SEARCH_VIDEO":
      if (!action.payload == "") {
        return {
          ...state,
          search: {
            exist: true,
            searchContent: state.trends
              .concat(state.originals)
              .filter((elemento) => {
                return elemento.title
                  .toLowerCase()
                  .includes(action.payload.toLowerCase());
              }),
          },
        };
      } else {
        return {
          ...state,
          search: {
            exist: false,
          },
        };
      }
<h3>Home.jsx:</h3>

En el home.jsx basicamente hago dos comprobaciones: una para ver si la propiedad 鈥榚xist鈥 de Search existe, si es as铆 hago otra segunda comprobacion para ver si hay algun elemento, si es as铆, mostrar los elemtnos, caso contrario mostrar un mensaje de que no hay resultados para la busqueda. En caso de que la primerae comprobacion NO se cumpla (si exist === false), se renderizan los elementos iniciales:

Este es todo el codigo de las comprobaciones en home.jsx:

{search.exist ? (
        <>
          {search.searchContent.length > 0 ? (
            <Categories title="B煤squeda">
              <Carousel>
                {search.searchContent.map((item) => (
                  <CarouselItem key={item.id} {...item} />
                ))}
              </Carousel>
            </Categories>
          ) : (
            <div className="notfound-message">
              <h1>No se ha encontrado ning煤n resultado para tu b煤squeda :(</h1>
              <img width="75" src={emoticonCaraTriste} alt="Emoticon triste" />
            </div>
          )}
        </>
      ) : (
        <>
          {myList.length > 0 && (
            <Categories title="Mi Lista">
              <Carousel>
                {myList.map((item) => (
                  <CarouselItem key={item.id} {...item} isList />
                ))}
              </Carousel>
            </Categories>
          )}
          <Categories title="Tendencias">
            <Carousel>
              {trends.map((item) => (
                <CarouselItem key={item.id} {...item} />
              ))}
            </Carousel>
          </Categories>
          <Categories title="Originales de Platzi Video">
            <Carousel>
              {originals.map((item) => (
                <CarouselItem key={item.id} {...item} />
              ))}
            </Carousel>
          </Categories>
        </>
      )}

Para la soluci贸n de search box a帽adi un atributo al Store que se llama filter, y cada vez que se escriba algo en el input, su valor se vaya a filter, y luego ese valor es leido por el componente Home, el mismo que filtra los videos en base a filter y muestra los videos filtrados:

// Search.jsx
const Search = ({ isHome, setFilter }) => {
  const inputStyle = classNames('input', {
    isHome,
  });
  const handleSearch = (event) => {
    setFilter(event.target.value);
  };
  return (
    <section className='main'>
      <h2 className='main__title'>驴Qu茅 quieres ver hoy?</h2>
      <input type='text' className={inputStyle} placeholder='Buscar...' onChange={handleSearch} />
    </section>
  );
};

const mapDispatchToProps = {
  setFilter,
};

export default connect(null, mapDispatchToProps)(Search);
// actions/index.js
export const setFilter = (payload) => ({
  type: 'SET_FILTER',
  payload,
});
// reducers/index.js
case 'SET_FILTER':
  return {
    ...state,
    filter: action.payload,
  };
// Home.jsx
const Home = ({ myList, trends, originals, filter }) => {
  const myListFiltered = myList.filter((item) =>
    item.title.toLowerCase().includes(filter.toLowerCase()),
  );
  const trendsFiltered = trends.filter((item) =>
    item.title.toLowerCase().includes(filter.toLowerCase()),
  );
  const originalsFiltered = originals.filter((item) =>
    item.title.toLowerCase().includes(filter.toLowerCase()),
  );
  return (
    <>
      <Header />
      <Search isHome />
      {myListFiltered.length > 0 && (
        <Categories title='Mi Lista'>
          <Carousel>
            {myListFiltered.map((item) => (
              <CarouselItem key={item.id} {...item} isList />
            ))}
          </Carousel>
        </Categories>
      )}
      {trendsFiltered.length > 0 && (
        <Categories title='Tendencias'>
          <Carousel>
            {trendsFiltered.map((item) => (
              <CarouselItem key={item.id} {...item} />
            ))}
          </Carousel>
        </Categories>
      )}
      {originalsFiltered.length > 0 && (
        <Categories title='Originales de Platzi Videos'>
          <Carousel>
            {originalsFiltered.map((item) => (
              <CarouselItem key={item.id} {...item} />
            ))}
          </Carousel>
        </Categories>
      )}
    </>
  );
};

const mapStateToProps = (state) => {
  return {
    myList: state.myList,
    trends: state.trends,
    originals: state.originals,
    filter: state.filter,
  };
};

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

para el reducer emple茅 levenshtein que lo explican en el curso profesional, para reducir el procesamiento disparo el evento solo cuando se pulsa enter, aunque lo he probado con onChange y funciona bien.

case ACTIONS.searchRequest:
            const listItems = state.trends.concat(state.originals);
            return{
                ...state,
                search: listItems.filter(item => {
                                            const l = new Levenshtein(item.title,action.payload)
                                            return l.distance < 4;
                                        })

            }```

Reto: quise hacer algo muy sencillo sin tener que meterme con mandar actions al store, y que pudiese ser r谩pido y minimalista sobre la misma pantalla:

Search.jsx:

import React, { useState } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import classNames from "classnames";
import "../assets/styles/components/Search.scss";

const Search = ({ isHome, available_videos }) => {
    const inputStyle = classNames("input", {
        isHome,
    });

    const [showResults, setShowResults] = useState(false);
    const [searchResults, setSearchResults] = useState([]);

    const searchVideos = (event) => {
        setSearchResults(
            available_videos.filter((video) =>
                video.title.toLowerCase().includes(event.target.value)
            )
        );
        setShowResults(event.target.value.length > 3);
    };

    return (
        <section className="main">
            <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
            <input
                type="text"
                className={inputStyle}
                placeholder="Buscar..."
                onChange={searchVideos}
            />
            {showResults && (
                <div className="search-results">
                    <ul>
                        {searchResults.length === 0 ? (
                            <li>Sin resultados</li>
                        ) : (
                            searchResults.map((item) => (
                                <li className="result">
                                    <Link to={`/player/${item.id}`}>
                                        {item.title}
                                    </Link>
                                </li>
                            ))
                        )}
                    </ul>
                </div>
            )}
        </section>
    );
};

const mapStateToProps = (state) => {
    return {
        available_videos: [...state.trends, ...state.originals],
    };
};

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

C贸digo agregado a Search.scss:

.search-results {
    background-color: #fff;
    min-height: 2em;
    width: 67%;
    top: -20px;
    position: relative;
    ul {
        display: block;
        width: 100%;
        list-style: none;
        padding: 0;
        list-style-type: none;
        li {
            padding: 10px 20px;
            &.result {
                display: block;
                a {
                    display: block;
                    height: 100%;
                    width: 100%;
                    color: #000;
                    text-decoration: none;
                }
                &:hover {
                    cursor: pointer;
                    background-color: #8f57fd;
                    a {
                        color: #fff;
                    }
                }
            }
        }
    }
}

Me parece que este video debe estar ni bien comienza la secci贸n de Redux, ser铆a de gran ayuda para ir us谩ndola con el desarrollo del curso.

A mi me daba un error con el Devtools y lo que hice fue esto

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() || compose 

redux dev tool 鈥 sin palabras, est谩 genial !

bueno termine de ver todo, vamos de nuevo a la practica

Tendr铆amos que haber arrancado con esta herramienta desde el principio porque me sirve mucho para entender realmente el flujo. Siento que hasta reci茅n solo copiaba cada paso

Me encant贸 el reto, aqu铆 dejo mi corta implementaci贸n de la b煤squeda de v铆deos.
El comportamiento que deseaba era:
1.) En el momento de comenzar una b煤squeda, solamente me muestra una secci贸n de b煤squeda con los resultados de la b煤squeda y oculta las otras secciones; aqu铆 incluye originals y trends.
2.) En caso que no est茅 realizando una b煤squeda o que la b煤squeda no contenga elementos, mostrar todas las secciones con normalidad.
https://github.com/canoaf98/PlaztiVideo/commit/4f2fce78fe1da9edf112dc5b977485153a44de35
El Feedback es bien preciado:))

Para el reto agregu茅 este caso en el reducer, que filtra en una nueva lista los videos que contengan el texto que se escribe en el input:

case SET_SEARCH_VALUE:
  return {
    ...state,
    searchList: [...state.trends, ...state.originals].filter((video) => {
      return video.title
        .toLowerCase()
        .includes(action.payload.toLowerCase());
    }),
  };	

Esta acci贸n la mando desde el input del componente Search 馃馃:

const Search = ({ setSearchValue }) => {
  const handleSearch = (e) => {
    setSearchValue(e.target.value);
  };

  return (
    <section className="l_search">
      <h2 className="l_search__title">驴Qu茅 quieres ver hoy?</h2>
      <input
        onChange={handleSearch}
        className="c_searcher"
        type="text"
        placeholder="Buscar..."
      />
    </section>
  );
};

const mapDispatchToProps = {
  setSearchValue,
};

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

Y renderizo esta nueva lista condicionalmente si tiene resultados:

return (
  <>
    <Search />

    {searchList?.length > 0 && (
      <Carousel title="Busqueda" videos={searchList} />
    )}

    {mylist?.length > 0 && (
      <Carousel title="Mi lista" videos={mylist} isMyList />
    )}

    <Carousel title="Tendencias" videos={trends} />
    <Carousel title="Originales" videos={originals} />
  </>
);

Reto superado

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

const store = createStore(reducer, initialState, composeEnhancers);

Search.jsx

import React from 'react';
import { connect } from 'react-redux';
import { searchRequest } from '../actions';
import classNames from 'classnames';
import Categories from './Categories';
import Carousel from './Carousel';
import CarouselItem from './CarouselItem';
import '../assets/styles/components/Search.scss';

const Search = props => {
  const { isHome, searchResult, } = props;
  const inputStyle = classNames('input', {
    isHome
  });

  const handleSearch = (event) => {
    if (event.key === 'Enter') {
      props.searchRequest(event.target.value);
    }
  };

  return (
    <section className="main">
      <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
      <input type="text" className={inputStyle} placeholder="Buscar..." onKeyUp={handleSearch} />

      {searchResult.length > 0 && (
        <Categories title='Resultados'>
          <Carousel>
            {searchResult.map((item) => (
              <CarouselItem
                key={item.id}
                {...item}
              />
            ))}
          </Carousel>
        </Categories>
      )}

    </section>
  );
}

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

const mapDispatchToProps = {
  searchRequest,
};

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

Actions

export const searchRequest = (payload) => ({
    type: 'SEARCH_REQUEST',
    payload,
});

Reducers

case 'SEARCH_REQUEST':
            return {
                ...state,
                searchResult: state.trends.filter((item) => item.title.toLowerCase().search(action.payload.toLowerCase()) !== -1) ||
                    state.originals.filter((item) => item.title.toLowerCase().search(action.payload.toLowerCase()) !== -1) ||
                    [],
            }

Este es mi handle para la b煤squeda dentro del Search component.

Donde searchVideos es el action que actualiza - ya sea con un arreglo filtrado de trends y originals o null - el estado. Puntualmente una propiedad que agregu茅 llamada search.

const handleSearch = e => {
    const { value } = e.target
    if(value) {
      const search = item => item.title.toLowerCase().includes(value.toLowerCase())
      const filtered = props.trends.filter(search).concat(props.originals.filter(search))
      props.searchVideos(filtered)
    } else {
      props.searchVideos(null)
    }
  } 

As铆 me quedaron los PropTypes del CarouselItem agregando los actions, el booleano que comprueba en qu茅 contexto se renderiza y los campos que son requeridos.

CarouselItem.propTypes = {
  id: PropTypes.number.isRequired,
  cover: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  year: PropTypes.number.isRequired,
  contentRating: PropTypes.string.isRequired,
  duration: PropTypes.number.isRequired,
  isMylist: PropTypes.bool,
  setFavorite: PropTypes.func.isRequired,
  deleteFavorite: PropTypes.func.isRequired,
};

Me encanta esa herramienta, muy bueno este curso

Reto Busqueda

Home

import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import Header from '../components/Header';
import Carousel from '../components/Carousel';
import CarouselItem from '../components/CarouselItem';
import Categories from '../components/Categories';
import Search from '../components/Search';
import '../assets/styles/Home.scss';

const Home = () => {
  const [allMovies, setAllMovies] = useState([]);

  const { myList, originals, trends, search } = useSelector(({ originals, myList, trends, search }) => {
    return {
      myList,
      originals,
      trends,
      search,
    }
  });

  useEffect(() => {
    const newArray = originals.concat(trends);
    setAllMovies(newArray.filter((element)=> element.title.includes(search.toString())));

  }, [search]);
  

  return (
    <>
      <Header />
      <Search isHome />
      {allMovies.length > 0 && search.trim().length > 0 && (
      <Categories title="Busqueda">
        <Carousel>
          {allMovies.map(item => (
            <CarouselItem
              key={item.id}
              {...item}
            />
             ))}
        </Carousel>
      </Categories>
       )}
      {myList.length > 0 && (
      <Categories title="Mi lista">
        <Carousel>
          {myList.map(item => (
            <CarouselItem
              key={item.id}
              {...item}
              isList
            />
            ))}
        </Carousel>
      </Categories>
    )}
      <Categories title="Tendencias">
        <Carousel>
          {trends.map(item => (
            <CarouselItem key={item.id} {...item} />
          ))}
        </Carousel>
      </Categories>
      <Categories title="Originales de Platfix">
        <Carousel>
          {originals.map(item =>
            <CarouselItem key={item.id} {...item} />
          )}
        </Carousel>
      </Categories>
    </>
)
};

export default Home;

Search.jsx

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { useDispatch } from 'react-redux'
import { postSearch } from '../actions/index';

import '../assets/styles/components/Search.scss';

const Search = ({ isHome }) => {
  const inputStyle = classNames('input', {
    isHome,
  });

  const dispatch = useDispatch();

  const [valueSearch, setValueSearch] = useState('');

  const handleChange = (event) => {
    setValueSearch(event.target.value);

    dispatch(postSearch(valueSearch));
  }

  return (
    <section className="main">
      <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
      <input type="text" className={inputStyle} placeholder="Buscar..." onChange={handleChange} />
    </section>
  );
}

Search.propTypes = {
  isHome: PropTypes.bool,
};

export default Search;```


Action



export const postSearch = (search) => ({
type: 鈥楽EARCH鈥,
payload: search,
})```

Reducer

    case 'SEARCH':
      return {
        ...state,
        search: action.payload,
      }```



Les dejo mi github

https://github.com/ruben-xe

Dejo mi soluci贸n para el reto es un buscador de coincidencias utilizando include para el input en los t铆tulos de los videos.

Funcion en Seacrh.jsx

     const handleInput = event => {
        const searching = event.target.value
        searchRequest(searching)
    }

Nueva categoria en el Home.jsx para poder mostrar la busqueda

{searched?.length > 0 && 
                <Category title="Coincidencias en la busqueda">
                    <Carousel>
                    {searched?.map(item => 
                        <CarouselItem key={item.id} {...item}/>
                    )}
                    </Carousel>
                </Category>
            }

Mi reducer

case 'SEARCH_REQUEST':
            return {
                ...state,
                searched: action.payload ? state.originals.concat(state.trends).filter(item => item.title.toLowerCase().includes(action.payload.toLowerCase())) : []
            }

Woooow 馃槷

Genial

Como propuesta del search:
En el state del store (initialState) a帽ado una propiedad searchText
En el componente Search hago un connect que cambiar铆a el estado searchText del store
Para evitar renders o ejecuciones en cada keypress podemos usar el hook useDebouncedCallback instalando e importando use-debounce y as铆 actualizar el estado s贸lo cuando el usuario termina de escribir varias letras.

Si el servicio de b煤squeda dependiera de un servicio as铆ncrono (e.g. request al servidor) podr铆amos mirar la documentaci贸n de Redux para manejar acciones as铆ncronas mediante redux-thunk middleware: https://redux.js.org/advanced/async-actions

RETO

  1. Agregue un nuevo item dentro de INITIAL_STATE => searchResult
  2. Agregue 2 actions/reducer
  • SEARCH_ REQUEST ( buscar titulos similares )

  • CLEAR_SEARCH ( limpiar la busqueda)



Search.jsx

import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { search, clearSearch } from '../actions/index';
import '../assets/styles/components/Search.scss';

const Search = ({ isHome, search, clearSearch }) => {
  const inputStyle = classNames('input', { isHome });

  const handleInput = (event) => {
    const query = event.target.value;
    if (query === '') {
      clearSearch();
    } else {
      search(query);
    }
  };
  return (
    <section className='main'>
      <h2 className='main__title'>驴What do you wanna see today?</h2>
      <input
        type='text'
        name='query'
        className={inputStyle}
        placeholder='Search...'
        onChange={handleInput}
      />
    </section>
  );
};

const mapDispatchToProps = {
  search,
  clearSearch,
};
export default connect(null, mapDispatchToProps)(Search);


Home.jsx

/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import { connect } from 'react-redux';
import Search from '../components/Search';
import Categories from '../components/Categories';
import Carousel from '../components/Carousel';
import CarouselItem from '../components/CarouselItem';
import '../assets/styles/Home.scss';
import Header from '../components/Header';

const Home = ({ mylist, trends, originals, searchResult }) => {
  return (
    <>
      <Header />
      <Search isHome />
      {searchResult.length > 0 && (
        <Categories title='Search Result'>
          <Carousel>
            {searchResult.map((item) => (
              <CarouselItem key={item.id} {...item} />
            ))}
          </Carousel>
        </Categories>
      )}
      {mylist.length > 0 && (
        <Categories title='Mi Lista'>
          <Carousel>
            {mylist.map((item) => (
              <CarouselItem key={item.id} {...item} isList />
            ))}
          </Carousel>
        </Categories>
      )}
      <Categories title='Tendencia'>
        <Carousel>
          {trends.map((item) => (
            <CarouselItem key={item.id} {...item} />
          ))}
        </Carousel>
      </Categories>

      <Categories title='Originales de platzi video'>
        <Carousel>
          {originals.map((item) => (
            <CarouselItem key={item.id} {...item} />
          ))}
        </Carousel>
      </Categories>
    </>
  );
};
const mapStateToProps = (state) => {
  return {
    mylist: state.mylist,
    trends: state.trends,
    originals: state.originals,
    searchResult: state.searchResult,
  };
};
export default connect(mapStateToProps, null)(Home);


Action

import {
  SET_FAVORITE,
  DELETE_FAVORITE,
  LOGIN_REQUEST,
  LOGOUT_REQUEST,
  REGISTER_REQUEST,
  GET_VIDEO_SOURCE,
  SEARCH_REQUEST,
  CLEAR_SEARCH,
} from '../types';

//INFO: redux-thunk - return function
// export const setFavorite = (payload) => (dispatch) => {
//   dispatch({
//     type: SET_FAVORITE,
//     payload: payload,
//   });
// };

//INFO: redux return plain js object
export const setFavorite = (payload) => ({
  type: SET_FAVORITE,
  payload,
});

//INFO: redux return plain js object
export const deleteFavorite = (payload) => ({
  type: DELETE_FAVORITE,
  payload,
});

//INFO: redux return plain js object
export const login = (payload) => ({
  type: LOGIN_REQUEST,
  payload,
});

//INFO: redux return plain js object
export const logout = (payload) => ({
  type: LOGOUT_REQUEST,
  payload,
});

//INFO: redux return plain js object
export const register = (payload) => ({
  type: REGISTER_REQUEST,
  payload,
});

//INFO: redux-thunk - return function
export const getVideo = (videoId) => (dispatch, getState) => {
  const { trends, mylist, originals } = getState();
  const search =
    trends.find((item) => item.id === Number(videoId.id)) ||
    mylist.find((item) => item.id === Number(videoId.id)) ||
    originals.find((item) => item.id === Number(videoId.id)) ||
    [];

  dispatch({
    type: GET_VIDEO_SOURCE,
    payload: search,
  });
};

export const search = (query) => (dispatch, getState) => {
  const { trends, originals } = getState();
  const videos = [...trends, ...originals];
  const search = videos.filter((item) => {
    return item.title.toLowerCase().includes(query.toLowerCase());
  });

  dispatch({
    type: SEARCH_REQUEST,
    payload: search,
  });
};

export const clearSearch = (payload) => ({
  type: CLEAR_SEARCH,
  payload,
});


Reducer

import {
  SET_FAVORITE,
  DELETE_FAVORITE,
  LOGIN_REQUEST,
  LOGOUT_REQUEST,
  REGISTER_REQUEST,
  GET_VIDEO_SOURCE,
  SEARCH_REQUEST,
  CLEAR_SEARCH,
} from '../types';

const reducer = (state, action) => {
  switch (action.type) {
    case SET_FAVORITE:
      return { ...state, mylist: [...state.mylist, action.payload] };
    case DELETE_FAVORITE:
      return {
        ...state,
        mylist: state.mylist.filter((items) => items.id !== action.payload),
      };
    case LOGIN_REQUEST:
      return { ...state, user: action.payload };
    case LOGOUT_REQUEST:
      return { ...state, user: {} };
    case REGISTER_REQUEST:
      return { ...state, user: action.payload };
    case GET_VIDEO_SOURCE:
      return { ...state, playing: action.payload };
    case SEARCH_REQUEST:
      return { ...state, searchResult: action.payload };
    case CLEAR_SEARCH:
      console.log('clean');
      return { ...state, searchResult: [] };
    default:
      return state;
  }
};
export default reducer;


Types

export const SET_FAVORITE = 'setFavorite';
export const DELETE_FAVORITE = 'deleteFromFavorite';
export const LOGIN_REQUEST = 'loginRequest';
export const LOGOUT_REQUEST = 'logoutRequest';
export const REGISTER_REQUEST = 'registerRequest';
export const GET_VIDEO_SOURCE = 'getVideo';
export const SEARCH_REQUEST = 'search';
export const CLEAR_SEARCH = 'clear';

muy buena herramientaaa

馃 Ac谩 el repo terminado (faltan los dos 煤ltimos retos) https://github.com/AndresCampuzano/React-Router-and-Redux/commits/master

Interesante

La soluci贸n de reto lo anexo:

  1. Anexe una funci贸n en el component Search.jsx y lo conecte.

En el archivo actions/index.js

En el reducers/index.js

En el objeto que se encuentra en index.js, anexe dos propiedades al objeto:

"tendencias":[],
    "searchActive":false,

En el componente Home.jsx

Soluci贸n al reto:

action-type

export const SEARCH_REQUEST = 'SEARCH_REQUEST'

Action

export const searchRequest = (payload) => ({
					type: actionTypes.SEARCH_REQUEST,
					payload,
				})

Reducer

case actionTypes.SEARCH_REQUEST:
			return {
				...state,
				search: action.payload.trim().length > 0 ?
					state.myList.filter((item) =>
						item.title
							.toLowerCase()
							.includes(action.payload.trim().toLowerCase())
					)
					.concat(
						state.trends.filter((item) =>
							item.title
								.toLowerCase()
								.includes(action.payload.trim().toLowerCase())
						)
					)
					.concat(
						state.originals.filter((item) =>
							item.title
								.toLowerCase()
								.includes(action.payload.trim().toLowerCase())
						)
					) : []
			}

Search

import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import {聽connect } from 'react-redux'

import { searchRequest } from '../actions' 

import '../assets/styles/components/Search.scss';

const Search = props => {
	const { isHome } = props
	const inputStyle = classNames('input', { isHome })

	const handleInput = event => {
		props.searchRequest(event.target.value)
	}

  return (
		<section className="main">
			<h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
			<input
				type="text" 
				className={inputStyle}
				placeholder="Buscar..."
				onChange={handleInput}
			/>
		</section>
	)
}

Search.propTypes = {
	isHome: PropTypes.bool
}

const mapDispatchToProps = {
	searchRequest,
}

export default connect(null, mapDispatchToProps)(Search)

Home

import React from 'react';
import { connect } from 'react-redux'
import Search from '../components/Search';
import Categories from '../components/Categories';
import Carousel from '../components/Carousel';
import CarouselItem from '../components/CarouselItem';
import '../assets/styles/App.scss';

const Home = ({ myList, trends, originals, search }) => {
	return (
		<>
			<Search isHome />
			{search.length > 0 && (
				<Categories title="Resultados encontrados">
					<Carousel>
						{search.map((item) => (
							<CarouselItem key={item.id} isFavorite {...item} />
						))}
					</Carousel>
				</Categories>
			)}
			{myList.length > 0 && (
				<Categories title="Mi Lista">
					<Carousel>
						{myList.map((item) => (
							<CarouselItem key={item.id} isFavorite {...item} />
						))}
					</Carousel>
				</Categories>
			)}
			<Categories title="Tendencias">
				<Carousel>
					{trends.map((item) => (
						<CarouselItem key={item.id} {...item} />
					))}
				</Carousel>
			</Categories>
			<Categories title="Originales de Platzi Video">
				<Carousel>
					{originals.map((item) => (
						<CarouselItem key={item.id} {...item} />
					))}
				</Carousel>
			</Categories>
		</>
	)
}

const mapStateToProps = (state) => {
  return {
		myList: state.myList,
		trends: state.trends,
		originals: state.originals,
		search: state.search
	}
}

export default connect(mapStateToProps)(Home)

Excelente herramienta para debuggear.

Nadie creo una vista nuevo solo para la busqueda ?

Les comparto como solucione el reto:

1.A帽adir al store un array, para recibir los resultados.

'searching': [],
  1. Praparar componente Search:
import React from 'react'
import { connect } from 'react-redux'
import classNames from 'classnames'
import { getVideoSearch } from '../actions'
import '../assets/styles/components/Search.scss'

const Search = (props, isHome) => {
  const inputStyle = classNames('input', {
    isHome,
  })

  const handleInput = (event) => {
    console.log(event.target.value)
    props.getVideoSearch(event.target.value)
  }
  return (
    <section className='main'>
      <h2 className='main__title'>驴Qu茅 quieres ver hoy?</h2>
      <input
        type='text'
        className={inputStyle}
        placeholder='Buscar...'
        onChange={handleInput}
      />
    </section>
  )
}

const mapDispatchToProps = {
  getVideoSearch,
}

export default connect(null, mapDispatchToProps)(Search)
  1. Crear el Action:
export const getVideoSearch = (payload) => ({
  type: 'GET_VIDEO_SEARCH',
  payload,
})
  1. Crear el Reducer:
case 'GET_VIDEO_SEARCH':
      console.log(action.payload)
      return {
        ...state,
        searching: state.trends.filter((item) => item.title.toUpperCase().includes(action.payload)) ||
        state.originals.filter((item) => item.title.toUpperCase().includes(action.payload)),
      }
  1. Crear la vista el Home:
{
        Object.keys(searching).length > 0 ?
          (
            <Categories title='Video Encontrado'>
              <Carousel>
                {
                  searching?.map((item) => (<CarouselItem key={item.id} {...item} />))
                }
              </Carousel>
            </Categories>
          ) :
          null
      }
  1. En el mismo componente Home, a帽adir la propiedad searching.
const mapStateToProps = (state) => {
  return {
    myList: state.myList,
    trends: state.trends,
    originals: state.originals,
    searching: state.searching,
  }
}

Mi soluci贸n al reto:
Primero agregu茅 un objeto vac铆o en el estado inicial que hace referencia a ese video encontrado. Se llama videoSearched.
Action:

export const videoSearch = payload => ({
    type: 'VIDEO_SEARCH',
    payload
})

Reducer:

case 'VIDEO_SEARCH':
            return {
                ...state,
                videoSearched: state.trends.find(video => video.title.toLowerCase() === action.payload.trim().toLowerCase())
                || state.originals.find(video => video.title.toLowerCase() === action.payload.trim().toLowerCase())
                || {}
            }

Componente Search:

const Search = ({ isHome, videoSearch }) => {
  const inputStyle = classNames('input', {
    isHome
  })
  
  const handleOnChange = event => {
    videoSearch(event.target.value)
  }
  
  return (
    <section className="main">
      <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
      <input type="text" className={inputStyle} placeholder="Buscar..." onChange={handleOnChange}/>
    </section>
  );
}

const mapDispatchToProps = {
  videoSearch,
}
export default connect(null, mapDispatchToProps)(Search);

Componente Home:

const Home = ({ videoSearched, myList, trends, originals }) => {

  const isVideoSearched = Object.keys(videoSearched).length > 0
  return (
    <>
      <Search isHome />
      {isVideoSearched &&
        <Categories title="Video encontrado">
          <Carousel>
            <CarouselItem
              key={videoSearched.id}
              {...videoSearched}
              isVideoSearched />
          </Carousel>
        </Categories>
      }
      {myList.length > 0 &&
        <Categories title="Mi Lista">
          <Carousel>
            {myList.map(item =>
              <CarouselItem
                key={item.id}
                {...item}
                isList />
            )}
          </Carousel>
        </Categories>
      }
      <Categories title="Tendencias">
        <Carousel>
          {trends.map(item =>
            <CarouselItem key={item.id} {...item} />
          )}
        </Carousel>
      </Categories>
      <Categories title="Originales de Platzi Video">
        <Carousel>
          {originals.map(item =>
            <CarouselItem key={item.id} {...item} />
          )}
        </Carousel>
      </Categories>
    </>
  );
}
  
const mapStateToProps = state => {
  return {
    myList: state.myList,
    trends: state.trends,
    originals: state.originals,
    videoSearched: state.videoSearched,
  }
}
export default connect(mapStateToProps, null)(Home);

VISTA PARA LA BUSQUEDA

import React, { useEffect } from 'react'
import { Link } from 'react-router-dom'
import Header from '../components/Header'
import Carousel from '../components/Carousel'
import Carouselitem from '../components/Carouselitem'

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

import { searchVideo } from '../actions';

const ResultSearch = props => {

	// Traer el termino de busqueda
	const { string } = props.match.params

	// const [form, setValues] = useState({})

	useEffect(() => {
		// Esta funcion es del reducer 
		props.searchVideo(string)
	}, [])

	console.log(props.busqueda);

	return (
		<>
			<Header />
			<section className="Home">
				<div className="text-search">
					<h1>Resultados para : {string}</h1>
					<hr/>
				</div>
				{props.busqueda.length == 0 ?
						<div className="text-search">
							<h1>Upp ! No hay resultados para tu busqueda</h1>
						</div>
					:
					<Carousel>
						{props.busqueda.map((item) => (
							<Carouselitem key={item.id} {...item} />
						))}
					</Carousel>
				}
			</section>
		</>
	)
}

// El Reducer
const mapStateToProps = (state) => {
	return {
		busqueda: state.busqueda,
	}
}

// LA ACTION
const mapDispatchToProps = {
	searchVideo,
}

export default connect(mapStateToProps, mapDispatchToProps)(ResultSearch)```

REDUCES

case 'SEARCH_VIDEOS':
  return {
    ...state,
    busqueda:
      state.trends.filter((item) => item.title.toLowerCase().includes(action.payload.toLowerCase())) ||
      state.originals.filter((item) =>  item.title.toLowerCase().includes(action.payload.toLowerCase())) ||
      [],
  }

Al componente searh le agrege esto


import React,{useState} from 鈥榬eact鈥

import '鈥/assets/styles/components/Search.scss鈥
import { withRouter } from 鈥榬eact-router-dom鈥

const Search = (props) => {
const [form, setValues] = useState({

})

const handleInput = (event) => {
	setValues({
		...form,
		[event.target.name]: event.target.value,
	});
}

const handleSearch = (e)=>{
if(e.charCode == 13){
props.history.push(search/${form.searh})
}
}

return (
<section className=鈥渕ain鈥>
<h2 className=鈥渕ain__title鈥>驴Qu茅 quieres ver hoy?</h2>
<input type="text"
name="searh"
onChange={handleInput}
onKeyPress={handleSearch}
className="input main__input"
placeholder=鈥淏uscar鈥︹ />
</section>
)
}

export default withRouter(Search)




El action solo es add el name del reducer


![Busqueda 1](https://drive.google.com/file/d/1op7f8EUFlOBqbDhXf9dvq_7xqAkvHrBa/view?usp=sharing)


![Busqueda 2](https://drive.google.com/file/d/1ulULbfXZ1doeoEgykGRE2CB4BJlERwQy/view?usp=sharing)

Genial me ha gustado mucho el curso

Hola, una consulta. Qui茅n almacena TODO el HISTORIAL del STORE, ReduxDevtools o Redux?
Me intriga porque a mi parecer ser铆a ineficiente y muy pesado para el navegador que Redux est茅 guardando toda esa informaci贸n.
Gracias, compa帽eros.

Jajaja que buen uso del plugin de chrome, yo tuve que aprender a hacer un logger y pasarlo como middleware al createStore para poder debugear, pero esta extensi贸n se ve mucho mejor y te da muchas m谩s herramientas, gracias c:

Que reto m谩s interesante, mi soluci贸n fue la siguiente

En mi archivo index.js en donde se encuentra el initialState, agregu茅 un 鈥榮earch鈥: [] para que redux se encargue de la busqueda.

Dentro de mi archivo de actions sume 2 acciones:

export const getSearch = (payload) => ({
  type: 'GET_SEARCH',
  payload,
})

export const emptySearch = (payload) => ({
  type: 'EMPTY_SEARCH',
  payload,
})

Luego en mi archivo de reducers sume 2:

    case 'GET_SEARCH':
      return {
        ...state,
        search: state.trends.concat(state.originals).filter((item) => item.title.toLowerCase().search(action.payload.toLowerCase()) !== -1),`
      }`
    case 'EMPTY_SEARCH':
      return {
        ...state,
        search: [],
      }

Y al final agregu茅 lo siguiente al archivo de Home.jsx

{search.length > 0 && (
        <Categories title='Resultados de la busqueda...'>
          <Carousel>
            {search.map((item) =>
              <CarouselItem
                key={item.id}
                {...item}
                isList
              />)}
          </Carousel>
        </Categories>
      )}
/* {...} */
// Trae cada uno de los elementos que necesito del estado
const mapStateToProps = (state) => {
  return {
    search: state.search,
    myList: state.myList,
    trends: state.trends,
    originals: state.originals,
  }
}

Tuve que poner una acci贸n de empty search para que el componente renderizado se me eliminara, ya que cuando hac铆a el getSearch y luego vaciaba el input, en el 煤ltimo proceso me arrojaban todos como resultado, por que se cumpl铆a (no entiendo por que) el filtro que pasaba.

Redux Dev Tools nos va a servir mucho para entender mejor el flujo de nuestra informaci贸n en nuestra aplicaci贸n y poder realizar debugging de manera sencilla.

Solamente necesitas instalar la extensi贸n seg煤n el navegador que tengas:

Chrome
Firefox

Una vez instalado dentro de nuestro index.js vamos a a帽adir el siguiente c贸digo:

// importamos compose
import { createStore, compose } from 鈥榬edux鈥;

const composeEnhancers = window.REDUX_DEVTOOLS_EXTENSION_COMPOSE || compose

const store = createStore(reducer, initialState, composeEnhancers

Para que el reducer no tenga que elegir entre los resultados de la busqueda en trends o en originals

      return {
        ...state,
        search: state.trends.filter(item => item.title.toLowerCase().includes(action.payload)).concat(state.originals.filter(item => item.title.toLowerCase().includes(action.payload))) || []
      }

https://app.gitbook.com/@fernan942/s/react-router-and-redux/

Chicos, como est谩n? en este link les dejo mis apuntes del curso, espero les ayude en algo. No es perfecto, pero puede ser una gu铆a.

Una forma en la que pueden buscar tanto por t铆tulo como por nombre es filtrando y regresando siempre una lista y despu茅s recorrerla a al hora de hacer render en el componente:
Reducer:

    case typeAction.findVideo:
      const allVideos = state.trends.concat(state.originals);
      return {
        ...state,
        searchMatch: [].concat(
          allVideos
            .find((item) => (
              item.title.toLowerCase() === action.payload.toLowerCase()
            )) ||
          allVideos
            .filter((item) => (
              item.type.toLowerCase() === action.payload.toLowerCase()
            )),
        ),
      };

Componente:

const Search = ({ findVideo, searchMatch }) => {
  const [input, setInput] = useState('');
  const [findActive, setFindActive] = useState(false);
  const handleInput = (event) => {
    event.preventDefault();
    setInput(event.target.value);
    findVideo(input);
    setFindActive(true);
  };

  return (
    <>
      <section className='main'>
        <h2 className='main__title'>驴Qu茅 quieres ver hoy?</h2>
        <input
          type='text'
          className='input'
          placeholder='Buscar...'
          onChange={handleInput}
        />
      </section>
      {findActive ? (
        searchMatch.length ? (
          <>
            <br />
            <Categories title='Resultados'>
              <Carousel>
                {searchMatch.map((item) => (
                  <CarouselItem key={item.id} {...item} />
                ))}
              </Carousel>
            </Categories>
          </>
        ) :
          (
            <>
              <Categories title='Ning煤n resultado coincide con la b煤squeda' />
              <br />
            </>
          )
      ) : null
      }
    </>
  );
};

Y antes de que alguien se lo pregunte, al usar el mismo motor de Chrome, esta extensi贸n tambi茅n funciona en MS Edge 馃槂

Que buena herramienta!

Esta genial esa extensi贸n!

Reto

Index.js

Agrergar en initialState la variable searchResults.

Componente: Search

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { findVideo } from '../actions';

import '../assets/styles/components/Search.scss';

const Search = ({ isHome, findVideo }) => {

    const inputStyle = classNames('input', {
        isHome
    });

    const handleFindVideo = (e) => {
        findVideo(e.target.value.toLowerCase());
    }

    return (
        <section className="main">
            <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
            <input type="text" className={inputStyle} placeholder="Buscar..." onChange={handleFindVideo} />
        </section>
    );
}

Search.propTypes = {
    isHome: PropTypes.bool,
    findVideo: PropTypes.func,
}

const mapDispatchToProps = {
    findVideo
}

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

Componente: Home

import React from 'react';
import { PropTypes } from 'prop-types';
import { connect } from 'react-redux';

import Header from '../components/Header';
import Search from '../components/Search';
import Categories from '../components/Categories';
import Carousel from '../components/Carousel';
import CarouselItem from '../components/CarouselItem';

import '../assets/styles/containers/Home.scss';

const Home = ({ myList, trends, originals, searchResults }) => {

    return (
        <>
            <Header />
            <Search isHome />

            {searchResults.length > 0 &&
                <Categories title="Resultados de la b煤squeda">
                    <Carousel>
                        {searchResults.map(item =>
                            <CarouselItem key={item.id} {...item} />
                        )}
                    </Carousel>
                </Categories>
            }

            {myList.length > 0 &&
                <Categories title="Mi lista">
                    <Carousel>
                        {myList.map(item =>
                            <CarouselItem key={item.id} {...item} isList />
                        )}
                    </Carousel>
                </Categories>
            }

            <Categories title="Tendencias">
                <Carousel>
                    {trends.map(item =>
                        <CarouselItem key={item.id} {...item} />
                    )}
                </Carousel>
            </Categories>

            <Categories title="Originales">
                <Carousel>
                    {originals.map(item =>
                        <CarouselItem key={item.id} {...item} />
                    )}
                </Carousel>
            </Categories>
        </>
    )
};

Home.propTypes = {
    myList: PropTypes.array,
    trends: PropTypes.array,
    originals: PropTypes.array,
    searchResults: PropTypes.array,
}

const mapStateToProps = state => {
    return {
        myList: state.myList,
        trends: state.trends,
        originals: state.originals,
        searchResults: state.searchResults,
    };
};

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

Reducer: findVideo

case actions.findVideo:
  if (action.payload.length === 0) {
    return {
      ...state,
      searchResults: []
    }
  }
  return {
    ...state,
    searchResults: state.trends.concat(state.originals).filter(item => (item.title.toLowerCase()).includes(action.payload))
    || []
  }

Action: findVideo

// ..
export const findVideo = payload => ({
    type: actions.findVideo,
    payload,
});
// ..

Lo logr茅, de la siguiente forma 馃榿馃榿馃榿
##Aument茅 un atributo al estado, lo llam茅 鈥渟earched鈥

{
    "user": {},
    "playing": {},
    "searched":[],
    "myList": [],
    "trends": [
        {
            "id": 2,
            "slug": "tvshow-2",
            "title": "In the Dark",
            "type": "Scripted",
            "language": "English",
            "year": 2009,
            "contentRating": "16+",
            "duration": 164,
            "cover": "http://dummyimage.com/800x600.png/99118E/ffffff",
            "description": "Vestibulum ac est lacinia nisi venenatis tristique",
            "source": "https://mdstrm.com/video/58333e214ad055d208427db5.mp4"
        }

##Reducer que utilic茅

  case "SEARCH_VIDEOS":
            console.log(action.payload);
            return {
                ...state,
                searched: state.trends.filter((item) => item.title.toLowerCase() === action.payload.toLowerCase())
                    || state.originals.filter((item) => item.title.toLowerCase() === action.payload.toLowerCase())
                    || [],
            }

##Action que que puse

export const searchVideos = payload => ({
    type: "SEARCH_VIDEOS",
    payload
})

##Aumente una secci贸n m谩s en el home para que aparezcan las coincidencias

 <div className="App">
      <Header />
      <Search isHome />
      {/* IMPLEMENTANDO LA B脷SQUEDA DE VIDEOS  */}
      {searched.length > 0 &&
        <Categories title="Resultados de la b煤squeda">
          <Carousel>
            {searched.map(item => (
              <CarouselItem
                key={item.id}
                {...item}
              />))}
          </Carousel>
        </Categories>}
      {myList.length > 0 &&
        <Categories title="Mi Lista">
          <Carousel>
            {myList.map(item =>
              <CarouselItem
                key={item.id}
                {...item}
                isList
              />
            )}
          </Carousel>
        </Categories>
      }
      <Categories title="Tendencias">
        <Carousel>
          {trends.map(item =>
            <CarouselItem key={item.id} {...item} />
          )}
        </Carousel>
      </Categories>
      <Categories title="Originales de Platzi Video">
        <Carousel>
          {originals.map(item =>
            <CarouselItem key={item.id} {...item} />
          )}
        </Carousel>
      </Categories>
    </div>

Debug conRedux Devtools => Nos va a sservir para saber como esta el flujo de la informaci贸n de nuestra aplicaci贸n y vamos a poder trabajar con Redux , vamos a poder ver en tiempo real como se estan ejecutando nuestras acciones!!!

muy buena herramienta, incre铆ble !

redux DevTools, alguien lo utiliza en Safari?

Wow!! Desconoc铆a completamente de redux devtools y me soprendi贸 much铆simo con la estructura de 谩rbol, excelente!

He estado intentando hacer lo del buscador y el renderizado del caroulselItem pero no lo consigo, creo que no se est谩 llamando al action pero no se por qu茅 驴observaciones?

Cree un arreglo en index.js para guardar los resultados de busqueda

initialState.searchResults = [];

Componente Search

import React from 'react'
import classNames from 'classnames';
import { connect } from 'react-redux';
import Categories from './Categories';
import Carousel from './Carousel';
import CarouselItem from './CarouselItem';

import { getVideoSearch } from '../actions';

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

const Search = props => { 

    const { isHome, searchResults } = props;

    const inputStyle = classNames('input', {
        isHome,
    })

    const handleChange = event => {
        if(event.key === 'Enter'){
            getVideoSearch(event.target.value); 
        }
    };

    console.log(`results: ${searchResults} `);

    return (
        <section className="main">
            <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
            <input 
                className={inputStyle} 
                type="text" 
                placeholder="Buscar..." 
                onKeyUp={handleChange}
            /> 

            {
                Object.keys(searchResults).length > 0 && (
                    <Categories title="Resultados de la b煤squeda">
                        <Carousel>
                            {searchResults.map(item =>
                                <CarouselItem 
                                key={item.id}
                                {...item}
                                />    
                                )}
                        </Carousel>
                    </Categories>
                )
            }
        </section>
    );
}; 

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

const mapDispatchToProps = {
    getVideoSearch,
};

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

Action

export const getVideoSearch = payload => ({
    type: 'GET_VIDEO_SEARCH',
    payload,
})

Reducer

case 'GET_VIDEO_SEARCH' :
            return {
                ...state,
                searchResults: state.trends.concat(state.originals).filter(item => item.title.toLowerCase().includes(action.payload.toLowerCase())),
            };
            break;
        }```


Mi soluci贸n al reto:
En el Search.jsx cambi茅 el input type a 鈥榮earch鈥 y le agregue un 鈥榙atalist鈥 que se despliega con los resultados de la busqueda, la cual se realiza mientras escribes en el input.

import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { searchVideo } from '../actions';
import '../assets/styles/components/Search.scss';

const Search = ({ isHome, searchresults, searchVideo }) => {
  const inputStyle = classNames('input', {
    isHome,
  });

  const handleResults = (event) => {
    searchVideo(event.target.value);
  };

  return (
    <section className='main'>
      <h2 className='main__title'>驴Qu茅 quieres ver hoy?</h2>
      <input
        type='search'
        className={inputStyle}
        placeholder='Buscar...'
        onChange={handleResults}
        //ref={(element) => (element || {}).onsearch = handleResults}
        list='data'
      />
      <datalist id='data'>
        {searchresults.map((item, key) => <option key={key} value={item.title} />)}
      </datalist>
    </section>
  );
};

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

const mapDispatchToProps = {
  searchVideo,
};

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

En el Home.jsx agregu茅 la l贸gica para desplegar los resultados de la b煤squeda con renderizado condicional validando si existen resultados:

 {
        searchresults.length > 0 && (
          <Categories title='Resultados de la b煤squeda'>
            <Carousel>
              {
                searchresults.map((item) => (
                  <CarouselItem
                    key={item.id}
                    {...item}
                  />
                ))
              }
            </Carousel>
          </Categories>
        )
      }`

En el estado inicial agregue 鈥渟earchresults鈥: [] para guardar los resultados de la b煤squeda.

Cre茅 un action para buscar el video:

export const searchVideo = (payload) => ({
  type: 'SEARCH_VIDEO',
  payload,
});```

Y el reducer que filtra los resultados 鈥榗ase insensitive鈥, concatenando los resultados de 鈥榯rends鈥 y de 鈥榦riginals鈥:

case 'SEARCH_VIDEO':
      return {
        ...state,
        searchresults: (action.payload !== '' ? (state.trends.filter((item) => item.title.toLowerCase().includes(action.payload.toLowerCase()))
          .concat(state.originals.filter((item) => item.title.toLowerCase().includes(action.payload.toLowerCase()))) || []) :
          []),
      };

Saludos!

Mi solucion al reto no fue de lo mas optima.

  1. cree un estado searchResults en el store.
  2. cree 2 actions: GET_SEARCH_RESULTS y CLEAR_SEARCH_RESULTS.
  3. implemente los reducers.
  4. En el home agregue una categoria mas 鈥淓NCONTRADOS鈥 , que muestro lo que haya en searchResults.
  5. Modifique el componente SEARCH.jsx para que implemente las llamas a los actions del punto 2.
  6. Al input para buscar le coloque un onChange, que llama a un handleInput , que es el que se encargar de hacer la busqueda.

Tenemos que descargar e instalar las Redux DevTools en la Chrome Web Store.

En el archivo principal index.js:

const composeEnhacers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && window.__REDUX_DEVTOOLS_EXTENSION__();

const store = createStore(reducer, initialState, composeEnhacers);

Yo lo utilic茅 para saber porque no pasaban los datos del email a gravatar y el problema fueron los connect jaja 馃ぁ

Reto solucionado:
Se agrega un arreglo de search en el estado inicial para manejar los resultados de la b煤squeda:

"search": [],

Seguidamente en Search debemos conectar nuestro componente, traer la funci贸n que usaremos y manejar el evento de onChange para el input:

import { connect } from 'react-redux';
import { searchRequest } from '../actions';
... 
// Funcion para manejar evento onChange
const handleInput = (event) => {
  props.searchRequest(event.target.value);
};
...
// Asi quedara el input
<input 
  className={inputStyle} 
  type='text' 
  placeholder='Buscar...' 
  onChange={handleInput}
/>
...
// Conectamos el componente y le pasamos lo necesario
const mapDispatchToProps = {
  searchRequest,
};

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

Luego en Home debemos crear un nuevo carousel para los resultados:

// Traemos search de nuestras props
const Home = ({ myList, trends, originals, search, searchRequest }) => {
// Validamos que hayan resultados
  const hasSearch = search.length > 0;
  ...
// Colocamos nuestro carousel
{hasSearch &&
  <Categories title='Resultados'>
          <Carousel>
            {search.map((item) => 
           <CarouselItem key={item.id} {...item} />,
           )}
       </Carousel>
     </Categories>
   }
}
// Traemos a search del estado:
const mapStateToProps = ({ myList, trends, originals, search }) => ({
  myList,
  trends,
  originals,
  search,
});

Ahora para poder realizar estas acciones debemos definir nuestro action:

export const searchRequest = (payload) => ({
  type: 'SEARCH_REQUEST',
  payload,
});

Y nuestro case en el reducer:

case 'SEARCH_REQUEST':
   const fullList = [...state.trends, ...state.originals];
   if (action.payload === '' || action.payload === ' ') return { ...state, search: [] };
   return {
     ...state,
     search: fullList.filter((item) => 
     item.title.toLowerCase().includes(action.payload.toLowerCase())) 
     || [],
   };

Ya hasta este punto deber铆a funcionar correctamente, pero note que al realizar una b煤squeda, si no borras todo el input y vas a otra secci贸n, digamos login o al player, al regresar la seccion de los resultados seguir谩 ah铆 ya que en el estado aun est谩n nuestros results en el objecto search, para solucionar esto en Home utilice useEffect y una validacion para apenas se monte el componente, si hay resultados en search, llame nuestra funcion de searchRequest con un string vacio, y asi se reinicie el esado de los resultados:

// Importamos useEffect
import React, { useEffect } from 'react';
...
// utilizamos useEffect para ejecutar nuestra funcion
useEffect(() => { 
  if (hasSearch) searchRequest('');
}, []);
// Y no olvidemos definir mapDispatchToProps para tener acceso a nuestra action
const mapDispatchToProps = {
  searchRequest,
};

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

De esta manera ese bug queda solucionado, si alguien sabe mejores maneras de resolverlo estare atento, y recuerden, nunca paren de aprender 馃槃

Este es mi resultado, solo modifique el 鈥淪earch鈥

import React, {useState, useMemo} from 'react';
import { connect } from 'react-redux';
import CarouselItem from './CarouselItem';

import './styles/Search.scss'
const Search = (props) =>{
    const [result, setResult] = useState({});
    const [search, setSearch] = useState({search: ''});
    const handleSearch = e =>{
        setSearch({
            ...search,
            [e.target.name]: e.target.value
        })
    }
    useMemo(() =>{
        setResult(props.trends.concat(props.originals).filter(item =>{
            return item.title.toLowerCase().includes(search.search.toLowerCase());
        }))
    },[search])
    return(
    <section className="main">
        <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
        <input 
            name="search"
            onChange={handleSearch} 
            type="text" 
            className="input" 
            placeholder="Buscar..."/>
        <div className="main-search">
            <div className="carousel__container">
                {!search.search ? 
                ''
                :
                    result.map((item) => {
                        return <CarouselItem
                                    key={item.id}
                                    {...item}
                                />
                    })
                }
            </div>
        </div>
    </section>
    )
}
const mapStateToProps = (state) =>{
    return{
        trends: state.trends,
        originals: state.originals,
    };
}
export default connect(mapStateToProps,null)(Search);

MEGA TIP: Ahora es mucho m谩s facil y m谩s limpio. Ademas de instalar la extension en el navegador, la configuracion que hay que hacer en nuestro proyecto es la siguiente:
.
Se instala el siguiente paquete:
.

npm install --save redux-devtools-extension

.
Y se usa de la siguiente manera:
.

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(
  reducer,
  composeWithDevTools(
    applyMiddleware(...middleware)
    // other store enhancers if any
  )
); 

Aqu铆 le dejo el link de la doc oficial del paquete en la web de NPM:
https://www.npmjs.com/package/redux-devtools-extension
.
Espero que les sea 煤til 馃挌馃挌馃挌馃馃槑

Para el reto
En index.js agregue:

const initialState = {
    "user": {}, 
    "searchedList" : [], // este
    "search": '', // y este
    "loadingPlaying": true,
    "playing": {},
	.............

En Home, uso de searchedList y search

const Home = ({ myList, trends, originals, searchedList, search }) => {
  return (
    <>
      <Header />
      <Search />
      {searchedList.length > 0 && (
        <Categories title={`Buscando por ${search}`}>
          <Carousel>
            {searchedList.map((item) => (
              <CarouselItem key={item.id} {...item} isMyList={true} />
            ))}
          </Carousel>
        </Categories>
      )}
....
const mapStateToProps = (state) => {
  return {
    myList: state.myList,
    trends: state.trends,
    originals: state.originals,
    searchedList: state.searchedList,
    search: state.search,
  };
};

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

Luego, en Search. Por medio del event onChange setear el search del estado

const Search = (props) => {
  const { setSearch } = props;
  const location = useLocation();
  const inputStyle = location.pathname === "/" ? "input-home" : "";
  const handleOnChange = (e) => {
    setSearch(e);
  }
  return (
    <section className="main">
      <h2 className="main__title">驴Qu茅 quieres ver hoy?</h2>
      <input
        type="text"
        className={`input ${inputStyle}`}
        placeholder="Buscar..."
        onChange={(e) => handleOnChange(e.target.value)}
      />
    </section>
  );
};

const mapDispatchToProps = {
  setSearch,
};

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

Siguiente, el action: Mi action es diferente respecto al del curso, pero se entiende

export const setSearch = (inputValue) => async (dispatch, getState) => {
    if (!inputValue) {
        dispatch({
            type: types.search,
            payload: ''
        });
        dispatch({
            type: types.searchedList,
            payload: []
        });
        return;
    } else {
        dispatch({
            type: types.search,
            payload: inputValue
        });
        const { trends, originals } = getState();
        const selected = await trends.filter(item => item.title.toLowerCase().includes(inputValue.toLowerCase())) || originals.trends.filter(item => item.title.toLowerCase().includes(inputValue.toLowerCase())) || [];
        dispatch({
            type: types.searchedList,
            payload: selected
        });
    }
}

Finalmente, mi reducer

case types.search:
            return {
                ...state,
                search: action.payload
            }
        case types.searchedList:
            return {
                ...state,
                searchedList: action.payload
            }
....

Soluci贸n al reto
Implement茅 varias mejoras en cuanto a validaciones y gesti贸n del Store

WAOOOOOOOO, estoy enamorado de esta herramienta Redux Dev Tools!

MI__SOLUCI脫N
Agregar search:[] a initialState

Luego鈥
Agregar onChange:{handleSearch} al input del Buscador

_Pasamos los props de esta forma _

const Search = (props) => {

    const { trends, originals, isHome } = props```


_Creamos la funci贸n_

const handleSearch = event => {
props.cleanSearch()
const query = event.target.value

    if(query && query !== ' ') {
      const searchFiltered = trends.filter((item) => {
        return 	item.title.toLowerCase().includes(query.toLowerCase())
    }) || originals.filter((item) => {
        return item.title.toLowerCase().includes(query.toLowerCase())
    })
    searchFiltered.map((item) => {    
         props.searchRequest({...item})                     
    })  
    }  
}

_importamos **{ connect }** y lo pasamos as铆_


const mapStateToProps = state => {
return {
trends: state.trends,
originals: state.originals,
search: state.search,
}
}

const mapDispatchToProps = {
cleanSearch,
searchRequest
}

export default connect(mapStateToProps, mapDispatchToProps) (Search)```

En actions exportamos

export const searchRequest = payload => ({
    type:'SEARCH_REQUEST',
    payload,
})

export const cleanSearch = payload => ({
    type: 'CLEAN_SEARCH',
    payload,
})```

_En el **reducer** agregamos_


    case 'SEARCH_REQUEST':
        return {
            ...state,
            search: [...state.search, action.payload]
        }
    case 'CLEAN_SEARCH':
        return {
            ...state,
            search:[]
        }```

Y as铆 nos deber铆a quedar el Home.jsx

const Home = ({ myList, search, trends, originals }) => {
        return (
        <>
            <Header />
            <Search isHome />

            {search.length > 0 ?
                <Categories title="Resultados">
                    <Carousel>
                        {search.map(item =>
                            <CarouselItem 
                                key={item.id} 
                                {...item}
                            />
                        )}
                    </Carousel>
            </Categories> : 
            <>
            {myList.length > 0 &&
                <Categories title="Mi lista">
                    <Carousel>
                    {myList.map(item =>
                        <CarouselItem 
                        key={item.id} 
                        {...item} 
                        isList
                        />
                    )}
                    </Carousel>
                </Categories>     
            }
                <Categories title="Tendencias">
                    <Carousel>
                        {trends.map(item =>
                            <CarouselItem key={item.id} {...item} />
                        )}
                    </Carousel>
                </Categories>

                <Categories title="Originales de YourV-deo">
                    <Carousel>
                        {originals.map(item =>
                            <CarouselItem key={item.id} {...item} />
                        )}
                    </Carousel>
                </Categories>

            </>           
            }           
        </>
    )
}
const mapStateToProps = state => {
    return {
        myList: state.myList,
        trends: state.trends,
        originals: state.originals,
        search: state.search,
    }

}

export default connect(mapStateToProps, null)(Home)```

La herramienta esta super, una forma visual de ver el funcionamiento interno de las apps.

Tremando Plugin Est谩 Genial. Cuales debuggers se recomiendan para desarrollo de aplicaciones con react.

Redux DevTools es una herramienta necesaria para todo desarrollador.

Gracias al compa帽ero LeandroVidela, pues de la soluci贸n de 茅l me apoy茅 para djar hecha la m铆a.

Search.jsx

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

const Search = (props) => {
  const { searchVideo } = props;

  const handleInput = (event) => searchVideo(event.target.value);

  return (
    <section className='Main'>
      <div className='Main__container'>
        <h2 className='Main__container__title'>驴Qu茅 quieres ver hoy?</h2>
        <input
          className='Main__container__input'
          type='text'
          name='searchVideo'
          id='searchVideo'
          placeholder='Buscar'
          onChange={handleInput}
        />
      </div>
    </section>
  );
};

const mapDispatchToProps = {
  searchVideo,
};

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

reducers.js

case SEARCH_VIDEO:
      const videos = [ ...state.trends, ...state.originals ];

      if (action.payload === '') {
        return {
          ...state,
          searchVideo: [],
        };
      };

      return {
        ...state,
        searchVideo: videos.filter((item) => item.title.toLowerCase().includes(action.payload)),
      };

Home.jsx

const Home = ({ myList, trends, originals, searchVideo }) => {
  return (
    <>
      <Header />
      <Search />

      { searchVideo.length > 0 && (
        <Categories title='B煤squeda'>
          <Carousel>
            {searchVideo?.map((item) => {
              return (
                <CarouselItem key={item.id} {...item} />
              );
            })}
          </Carousel>
        </Categories>
      )}

      { myList.length > 0 && (
        <Categories title='Mi lista'>
          <Carousel>
            {myList?.map((item) => {
              return (
                <CarouselItem key={item.id} {...item} isList={true} />
              );
            })}
          </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>
    </>
  );
};

const mapStateToProps = (state) => {
  return {
    myList: state.myList,
    trends: state.trends,
    originals: state.originals,
    searchVideo: state.searchVideo,
  };
};

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

index.js

const initialState = {
  'user': {},
  'playing': {},
  'searchVideo': [],
  'myList': [],
  'trends': [
    {
      'id': 2,
      'slug': 'tvshow-2',
      'title': 'In the Dark',
      'type': 'Scripted',
      'language': 'English',
      'year': 2009,
      'contentRating': '16+',
      'duration': 164,
      'cover': 'http://dummyimage.com/800x600.png/99118E/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 3,
      'slug': 'tvshow-3',
      'title': 'Instinct',
      'type': 'Adventure',
      'language': 'English',
      'year': 2002,
      'contentRating': '16+',
      'duration': 137,
      'cover': 'http://dummyimage.com/800x600.png/302140/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 4,
      'slug': 'tvshow-4',
      'title': 'Grand Hotel',
      'type': 'Comedy',
      'language': 'English',
      'year': 2014,
      'contentRating': '16+',
      'duration': 163,
      'cover': 'http://dummyimage.com/800x600.png/5472FF/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 5,
      'slug': 'tvshow-5',
      'title': 'Stargate Atlantis',
      'type': 'Scripted',
      'language': 'English',
      'year': 2014,
      'contentRating': '16+',
      'duration': 194,
      'cover': 'http://dummyimage.com/800x600.png/B36F20/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 6,
      'slug': 'tvshow-6',
      'title': 'Final Space',
      'type': 'Scripted',
      'language': 'English',
      'year': 2017,
      'contentRating': '16+',
      'duration': 124,
      'cover': 'http://dummyimage.com/800x600.png/CCC539/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 7,
      'slug': 'tvshow-7',
      'title': 'The InBetween',
      'type': 'Drama',
      'language': 'English',
      'year': 2011,
      'contentRating': '16+',
      'duration': 179,
      'cover': 'http://dummyimage.com/800x600.png/FF7A90/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
  ],
  'originals': [
    {
      'id': 8,
      'slug': 'tvshow-8',
      'title': 'Stargate Atlantis',
      'type': 'Action',
      'language': 'English',
      'year': 2012,
      'contentRating': '16+',
      'duration': 148,
      'cover': 'http://dummyimage.com/800x600.png/306880/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 9,
      'slug': 'tvshow-9',
      'title': 'Alien Highway',
      'type': 'Action',
      'language': 'English',
      'year': 2019,
      'contentRating': '16+',
      'duration': 128,
      'cover': 'http://dummyimage.com/800x600.png/604180/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 10,
      'slug': 'tvshow-10',
      'title': 'Elementary',
      'type': 'Animation',
      'language': 'English',
      'year': 2011,
      'contentRating': '16+',
      'duration': 346,
      'cover': 'http://dummyimage.com/800x600.png/FF91BA/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 11,
      'slug': 'tvshow-11',
      'title': 'Strange Angel',
      'type': 'War',
      'language': 'English',
      'year': 2015,
      'contentRating': '16+',
      'duration': 226,
      'cover': 'http://dummyimage.com/800x600.png/45807C/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 12,
      'slug': 'tvshow-12',
      'title': 'Private Eyes',
      'type': 'Comedy',
      'language': 'English',
      'year': 2018,
      'contentRating': '16+',
      'duration': 190,
      'cover': 'http://dummyimage.com/800x600.png/577380/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
    {
      'id': 13,
      'slug': 'tvshow-13',
      'title': 'NCIS: Los Angeles',
      'type': 'Drama',
      'language': 'English',
      'year': 2010,
      'contentRating': '16+',
      'duration': 160,
      'cover': 'http://dummyimage.com/800x600.png/5472FF/ffffff',
      'description': 'Vestibulum ac est lacinia nisi venenatis tristique',
      'source': 'https://mdstrm.com/video/58333e214ad055d208427db5.mp4',
    },
  ],
};
Esta herramienta es lo m谩s hermoso que mis ojos han visto nunca.! 馃槀

Reto solucionado:

  • en index.js del src, donde se encuentra el objecto JSON de initialState declar茅 un key nuevo llamado 鈥渞esult鈥 en este se agregara el objecto del resultado de la busqueda.
  • Gener茅 un action llamado getVideoResult con type: GET_VIDEO_RESULT
  • en el reducer hice uso de las expresiones regulares para que identifiquen la palabra typeada en el input de search y luego hice uso del find para que me retornara el resultado de la validacion con .match()
            let search = action.payload || ''
            let regExp = new RegExp('\\b' + search + '\\b', 'gi')
            return {
                ...state,
                result: search.length ? [state.lists.trends.find(item => item.title.match(regExp)) || state.lists.originals.find(item => item.title.match(regExp)) || []] : []
            } 
  • Luego el componente de Search. hice lo usual para conectar con flux. agregue el mapDispatchToProps para enviar el string de la busqueda a la acci贸n en el reducer y mapStateToProps para recibir esos valores en mis props.
  • Hice uso de useState adentro de la funcion de handleSearch para guardar el estado de la busqueda. y agregarsela como evento onChange en el input de search
	const [search, setSearch] = useState('')
	const { isHome, result } = props
	const handleSearch = event => {
		setSearch(event.target.value)
	}

Hice uso de useEffect para que observe los cambios de la variable search y realizar la b煤squeda adentro de un timeout, el cual ejecuta props.getVideoResult despues de 1 segundo

	useEffect(() => {
		const handler = setTimeout(() => {
			props.getVideoResult(search)
		}, 1000);

		return () => {
			clearTimeout(handler)
		}
	}, [search])
  • En el return hice uso del shortcut de fragment para anidar el carrusel con el resultado de la b煤squeda
	return (
		<>
			<section className="main">
				<h2 className="main__title">{mainTitle}</h2>
				<input type="text" name="search" className={inputStyle} placeholder={inputPlaceholder} onChange={handleSearch} />
			</section>
			{result.length > 0 &&
				<Categories title="Resultados">
					<Carousel>
						{result.map(items => (
							<CarrouselItem key={items} {...items} />
						))}
					</Carousel>
				</Categories>
			}
		</>
	)

Tengo una idea para el filtrado de los datos mejor use unos arreglos de los datos temporales

const mapStateToProps = (state) => {
  console.log('state.search', state.search);
  console.log('state.trendsFilter', state.trendsFilter);
  return {
    myList: state.myList,
    trends: state.search ? state.trendsFilter : state.trends,
    originals: state.search ? state.originalsFilter : state.originals,
  };
};

el link de mi repo en el tag de los cambios

n

Gracias por el aporte

SI USAN FIREFOX
Para ver Redux en Firefox tienen que ir a inspeccionar elemento y les deberia aparecer al fnal