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 ‘redux’;  
...  
  
  
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 ‘searchResult’

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 ‘searchRequest’

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 “search” que tiene como valor un objeto con las propiedades ‘exist’ (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 ‘exist’ 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: ‘SEARCH’,
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 ‘react’

import '…/assets/styles/components/Search.scss’
import { withRouter } from ‘react-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=“main”>
<h2 className=“main__title”>¿Qué quieres ver hoy?</h2>
<input type="text"
name="searh"
onChange={handleInput}
onKeyPress={handleSearch}
className="input main__input"
placeholder=“Buscar…” />
</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 ‘search’: [] 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 ‘redux’;

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é “searched”

{
    "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 ‘search’ y le agregue un ‘datalist’ 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 “searchresults”: [] 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 ‘case insensitive’, concatenando los resultados de ‘trends’ y de ‘originals’:

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 “ENCONTRADOS” , 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 “Search”

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 “result” 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