0

I have used Redux for state management in my application - with React Hooks. I am able to hit the API and get the response back like below screenshots from my action creator logs:

search results image logged out

Here's the code of my component where I'm trying to display the results:

import { useState, useEffect } from "react";
import {Link} from 'react-router-dom';

import styled from "styled-components";

import {GoSearch} from 'react-icons/go';

import LoaderSpinner from "../components/LoaderSpinner";
import MovieItem from "../components/MovieItem";

import { RootStateOrAny, useDispatch, useSelector } from "react-redux";
import {fetchAllShows} from '../actions/movies';
import {searchMovieByTitle} from '../actions/search';

 const Home = () => {

   const [loading, setLoading] = useState(true);
   const [searchString, setSearchString] = useState('');
   const [isFromSearchResults, setIsFromSearchResults] = useState(false);

   const dispatch = useDispatch();
   const movies = useSelector((state: RootStateOrAny) => state.shows)
   const searchResults = useSelector((state: RootStateOrAny) => state.shows);

   useEffect(()=> {
      setLoading(true);
      dispatch(fetchAllShows());
      setIsFromSearchResults(false);
   }, [dispatch])

   const handleSearchChange = (e: any) => {
      e.preventDefault();
      setSearchString(e.target.value);
   }

   const findMovieByTitle = () => {
      dispatch(searchMovieByTitle(searchString));
      setIsFromSearchResults(true);
      setSearchString('');
   }

   console.log(isFromSearchResults);

   var start, max, paginatedArr=[], pageSize = 25;

   for(start = 0; max = movies.length, start < max; start += pageSize) {
      paginatedArr = movies.slice(start, start + pageSize);
   }

     return <HomeContainer>
       <div className="search-bar">
          <input 
            type="text" 
            placeholder="Search for a movie" 
            value={searchString}
            onChange={handleSearchChange}
          />
          <div className="search" onClick={findMovieByTitle}>
              <GoSearch />
          </div>
      </div>
       <div className="grid">
          {
            isFromSearchResults 
                ? <div>
                    {
                        searchResults.map((result: any, index: number) => {
                            console.log(result);
                            // console.log(result.show);
                            return <Link 
                                        key={index} 
                                        to={{pathname:`/movies/${result.show.id}`, 
                                        state: {movie: result.show}}} 
                                    >
                                      <MovieItem show={result.show} />
                                 </Link> 
                        })
                        // errors out in this return statement. It says the result (from array mapped out above is null) whereas the action creator is able to print out the full search queries
                    }
                </div>
                
                :  movies.length == 0 
                    ? <div className="loader">
                            <LoaderSpinner 
                                isLoading={loading} 
                                loadingText="Fetching Movies..."
                            />
                        </div>
                    // : movies.map((movie:any, index:number) => {
                    : paginatedArr.map((movie:any, index:number) => {
                        return <Link 
                                    to={{pathname:`/movies/${movie.id}`, 
                                    state: {movie: movie}}} key={index}
                                >
                            <MovieItem show={movie} />
                        </Link> 
                    })
           }
         </div>
      </HomeContainer>
  }

 export default Home;

Here's the code for my action creator to make the search API call:

import {
   SEARCH_MOVIE_BY_TITLE,
} from './types';

import ShowsService from '../services/ShowsService';

export const searchMovieByTitle = (title: string) => async (dispatch: any) => {
try {
    let response = await ShowsService.searchMovieByTitle(title);
    console.log(typeof(response.data));
    // console.log(response.data);
    const promise = response.data.map((items: any) => {
        // console.log(items);
        return items;
    })
    const searchArr = await Promise.all(promise);
    console.log(searchArr);
    dispatch({type: SEARCH_MOVIE_BY_TITLE, payload: searchArr});
} catch (err) {
    console.log(err);
}
}

The problem now lies in trying to parse the search results array and display it as a list of <MyComponent /> with movie passed as props. It shows undefined for each of the items passed. How do I resolve this?

Sample error log attached below:

display search results error

7
  • const movies = useSelector((state: RootStateOrAny) => state.shows); const searchResults = useSelector((state: RootStateOrAny) => state.shows) looks sketchy - you're accessing the same state twice under different names. Have you confirmed that these arrays (which hold the data that is rendered) contain the expected data? Commented Jul 26, 2021 at 21:20
  • const promise = response.data.map((items: any) => { return items; }); const searchArr = await Promise.all(promise); is pointless if that's all you're doing in that loop. Simplify to const searchArr = response.data;. Commented Jul 26, 2021 at 21:22
  • You have two very similar parts of your code that render the movies from an array - and these arrays appear to have the same data type. Yet, one of the codes assumes the movies are stored on a .shows property on the items, while the other code assumes the movie objects are stored directly in the array. Decide on one, then abstract that part into a helper function that you can call twice. Commented Jul 26, 2021 at 21:24
  • Yes, they have separate data to the best of my understanding. Also, I have tried just assigning searchArr the values of response.data. It was in my previous code, without much luck Commented Jul 26, 2021 at 21:25
  • Yes, your're right about the code duplicity part. Redux just made everything verbose for me rn. I'd have a big task on my hand abstracting things as they are Commented Jul 26, 2021 at 21:26

1 Answer 1

0

I looked into your code and it seems your logs were only in the searchMovieByTitle action. It seems there are no checks for the availability of data in the rendered view. Usually when you perform fetching actions, you also check if the data has been fetched successfully before starting to use it.

In your code, this could be done like this:

<div className="grid">
  {isFromSearchResults && searchResults?.length ? ( // added check for searchResult here, to make sure it has data
    <div>
      {
        searchResults.map((result: any, index: number) => {
          console.log(result);
          console.log(result.show?.id); // it is suggested to also check if the id exists in all the result.show objects
          return (
            <Link
              key={index}
              to={{
                pathname: `/movies/${result.show?.id}`, // optionally add the "?." before id in order to avoid crushes if the id doesn't exist, though this depends on your backend logic; if they must always have an id, no need to add the "?."
                state: { movie: result.show },
              }}
            >
              <MovieItem show={result.show} />
            </Link>
          );
        })
        // errors out in this return statement. It says the result (from array mapped out above is null) whereas the action creator is able to print out the full search queries
      }
    </div>
  ) : movies.length == 0 ? (
    <div className="loader">
      <LoaderSpinner isLoading={loading} loadingText="Fetching Movies..." />
    </div>
  ) : (
    // : movies.map((movie:any, index:number) => {
    paginatedArr.map((movie: any, index: number) => {
      return (
        <Link
          to={{ pathname: `/movies/${movie.id}`, state: { movie: movie } }}
          key={index}
        >
          <MovieItem show={movie} />
        </Link>
      );
    })
  )}
</div>

If you look closely, I've added a check for the length of the array, making sure the data is in there before starting to map it. Also added a check for the id, to see if all your result.show objects have it.

Sign up to request clarification or add additional context in comments.

2 Comments

You seem to be very right about this. This code block still errors out in console and according to my understanding it's because the search results had not arrived yet. I'm saying this because after the error is thrown, about 5-10 secs later, the log shows with results from my redux action creator. How can I display the data only when it has arrived? In Flutter/Dart, we have FutureBuilder to do just that for async operations. Thanks again
@Nzadibe Hey, I'm glad this helped narrow down the debugging process a bit. If this code block still crushes even when you add it where it used to be in the original code, then the best approach would be to add a log for searchResults right before return. Check how the result changes while the fetching is happening and when it ends. Normally searchResults.length should be enough as a condition to determine whether or not there is any data inside searchResults. Maybe there is data, but it's not in the right format, which causes the mapping process to crush.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.