skip to Main Content

I am making a movie review app. I have a Home component which renders card components and passes them the "movie" prop. The API call is inside of the Home component. Inside the card component I have a button which, on click, should send the movie prop to a different component called Watchlist. I get the error "handleAddToWatchlist is not a function". it is provided by the watchlist component and executed on click of the button. Please help.

Card component:

const Card = ({movie, handleAddToWatchList}) => {
    const { Title, Year, Actors, Genre, Image } = movie;

   
    const movieInfo = (event) => {
        event.preventDefault();
        console.log(movie);
    }
    
    
    
    return(
        <div className="card">
            <div className="card-image">
                <img src={Image} alt="interstellar"/>
            </div>

            <div className="card-info">
                <h2 className="movie-title">{Title}</h2>
                <p className="movie-year">Year: {Year}</p>
                <p className="movie-genre">Genre: {Genre}</p>
                <p className="movie-actor">Actor: {Actors}</p>
            </div>




            <div className="movie-buttons">
                <button ><span class="material-symbols-outlined">star</span></button>
                <button onClick={() => handleAddToWatchList(movie)}><span className="material-symbols-outlined">add</span></button>
            </div>
        </div>

)
}


export default Card;

Watchlist component:

const Watchlist = () => {
    const { watchlistData, setWatchlist } = useAppContext();

    const handleAddToWatchList = (movie) => {

        if (!watchlistData.some((item) => item.id === movie.imdbID)) {
            setWatchlist((prevData) => [...prevData, movie]);
          } else {
            console.log(`${movie.title} is already in the watchlist.`);
          }
    }



    return (
        <>
        <nav>
            <ul>
                <li> <Link to="/" style={linkStyle}> Log Out </Link> </li>
                <li> <Link to="/reviews" style={linkStyle}>My Reviews</Link>  </li>
                <li> <Link to="/home" style={linkStyle}> Home </Link></li>
            </ul>
        </nav>
    <div className="main-container">
        <div className="main">
            <div className="movie-container-with-filter">
                <div className="movie-container">
                    {watchlistData && watchlistData.length > 0 ? (

                        watchlistData.map((movie) => (
                             <Card
                             key={movie.imdbID}
                             movie={movie}
                             handleAddToWatchList={handleAddToWatchList}
                                  />
                        ))
                    ) : (
                        <p>No movies in the watchlist</p>
                    )
                        
                    }
                </div>
            </div>
       </div>
    </div
)

Home.js

const Home = () => {
    const [currentPage, setCurrentPage] = useState(1)
    const [movies, setMovies] = useState([])
    const [recordsPerPage] = useState(9)
    const [ searchfield, setSearchfield ] = useState('')
    const [dateFilter, setDateFilter] = useState(0);
    const [ selectedGenre, setSelectedGenre ] = useState("");

    const uniqueMovieIds = useRef(new Set());
    const apiKey = "apikey"

    const indexOfLastRecord = currentPage * recordsPerPage;
    const indexOfFirstRecord = indexOfLastRecord - recordsPerPage

    const movieContainerRef = useRef(null)


    /*const fetchMovies = async () => {
        try {
            const response = await fetch(`http://www.omdbapi.com/?s=action&page=${currentPage}&apikey=${apiKey}&plot=full`)
            const data = await response.json()
            console.log(data);
        
            if (data.Response === 'True' && Array.isArray(data.Search)) {
                 // Filter out duplicates by IMDb ID
                 const uniqueMovies = data.Search.filter(newMovie => !uniqueMovieIds.current.has(newMovie.imdbID));
                
                 // Update the Set with new IMDb IDs
                 uniqueMovies.forEach(newMovie => uniqueMovieIds.current.add(newMovie.imdbID));
 
                 // SetMovies with the combination of existing movies and unique new movies
                 setMovies(prevMovies => [...prevMovies, ...uniqueMovies]);
                }
              
            
         else {
                console.error("Error fetching movies: ", data)
            } 
        }catch (error) {
            console.error("Error fetching data: ", error);
        }

    }*/

    const fetchMovies = async () => {
        try {
          const response = await fetch(`http://www.omdbapi.com/?s=action&page=${currentPage}&apikey=${apiKey}`);
          const data = await response.json();
      
          if (data.Response === 'True' && Array.isArray(data.Search)) {
            const detailedMovies = await Promise.all(
              data.Search.map(async (newMovie) => {
                const detailsResponse = await fetch(`http://www.omdbapi.com/?i=${newMovie.imdbID}&apikey=${apiKey}`);
                const detailsData = await detailsResponse.json();
                console.log(detailsData);
                return detailsData;
              })
            );
      
            setMovies(prevMovies => [...prevMovies, ...detailedMovies]);
          } else {
            console.error("Error fetching movies: ", data);
          }
        } catch (error) {
          console.error("Error fetching data: ", error);
        }
      };

      //FILTERING

      const handleGenreFilter = (event) => {
        const selectedGenre = event.target.value;
        setSelectedGenre(selectedGenre)
      }

      const onSearchChange = (event) => {
        setCurrentPage(1);
        uniqueMovieIds.current.clear();
        setSearchfield(event.target.value);
      }

      const handleYearFilter = (event) => {
        const dateFilter = event.target.value;
        setDateFilter(dateFilter);
      }

      const applyFilters = () => {

        if(searchfield === "" || dateFilter === 0 || selectedGenre === "") {
            return movies
        }
        console.log("Year:", dateFilter);
        console.log("Genre:", selectedGenre);
        console.log("Search:", searchfield);
        let filteredMovies = movies;
        console.log("Movies: ", filteredMovies);

        if (searchfield !== "") {
            filteredMovies = filteredMovies.filter(movie => movie.Title.toLowerCase().includes(searchfield.toLowerCase()));
          }
          console.log("Fiultered by search: ",filteredMovies);
      
          if (dateFilter !== 0) {
            filteredMovies = filteredMovies.filter(movie => movie.Year == dateFilter);
          }
          console.log("Fiultered by date: ",filteredMovies);
      
          if (selectedGenre !== '') {
            const selectedGenresArray = selectedGenre.split(",").map(genre => genre.trim())

            filteredMovies = filteredMovies.filter(movie => {
                const movieGenresArray = movie.Genre.split(",").map(genre => genre.trim())
                return selectedGenresArray.some(selectedGenre => movieGenresArray.includes(selectedGenre))
            });
          }
      
          setMovies(filteredMovies);
          console.log("FilteredMovies",filteredMovies);
      }

      const handleFilterButtonClick = () => {
        applyFilters();
      }
    
      const handleResetButtonClick = () => {
        setSearchfield("");
        setDateFilter(0)
        setSelectedGenre("")
        

        applyFilters();
      }

    
    
    const loadMore = () => {
        setCurrentPage(prevPage => prevPage + 1);
        
        if (movieContainerRef.current) {
            const containerTop = movieContainerRef.current.offsetTop;

            if(window.innerWidth <= 600) {
                document.getElementById("mainContainer").scrollIntoView({
                    behavior:"smooth",
                });
            } else {

                movieContainerRef.current.scrollTo({
                    top: 0,
                    behavior: 'smooth',
                });
            }
        } 
    }


    useEffect(() => {
        fetchMovies()

    }, [currentPage])

    const handleAddToWatchList = () => {
        console.log("works");
    }


    return (
        <>
        <nav>
            <ul>
                
                <li className="nav-links-text"> <Link to="/" style={linkStyle}> Log Out </Link> </li>
                <li className="nav-links-text"> <Link to="/watchlist" style={linkStyle}>Watchlist </Link>  </li>
                {/*kad napravis usere, postavi path da bude "/:uid/watchlist"*/}
                <li className="nav-links-text"> <Link to="/reviews" style={linkStyle}> My Reviews </Link></li>

                <li className="title-home">mrabyEMIR</li>
            </ul>
        </nav>
    <div id="mainContainer" className="main-container">
        <div className="main">
            <div className="movie-container-with-filter">
                <div ref={movieContainerRef} className="movie-container">
                    <div className="movies">
                        {
                        
                        movies.slice(indexOfFirstRecord, indexOfLastRecord).map((movie) => {
                            
                            return <Card key={movie.imdbID}

                             movie={{
                                  
                                  Image:movie.Poster,
                                  Title:movie.Title,
                                  Year:movie.Year,
                                  Actors:movie.Actors,
                                  Genre:movie.Genre
                                    
                            }}
                            />
                        })
                
                    }
                    </div>
                    </div>

                <div className="movie-filter">
                    <label>Search</label>
                    <input onChange={onSearchChange} type="search" style={{color:"black"}}/>
                    <label>Year:</label>
                    <input type="number" style={{color:"black"}} onChange={handleYearFilter}/>


                    <label>Genre</label>
                    <select style={{color:"black"}} onChange={handleGenreFilter}>
                        <option value="">Select Genre</option>
                        <option value="Adventure">Adventure</option>
                        <option value="Western">Western</option>
                        <option value="Musical">Musical</option>
                        <option value="Thriller">Thriller</option>
                        <option value="Sci-Fi">Sci-Fi</option>
                        <option value="Action">Action</option>
                        <option value="Horror">Horror</option>
                    </select>

                    <button onClick={handleFilterButtonClick}>FILTER</button>
                    <input type="reset" id="reset" value={"RESET"} style={{color:"black"}} onClick={handleResetButtonClick}/>

                </div>
            </div>
        </div>

            <div className="load-more-container">
                    <button className="load-more" onClick={loadMore}>Next Page</button>
            </div>
                    
        </div>
        </>
    )
}



export default Home;

AppContext.js

const AppContext = createContext();

export function AppProvider({ children }) {
  const [watchlistData, setWatchlistData] = useState([]);

  return (
    <AppContext.Provider value={{ watchlistData, setWatchlistData }}>
      {children}
    </AppContext.Provider>
  );
}

export function useAppContext() {
  return useContext(AppContext);
}

index.js

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <AppProvider>
      <App/>
    </AppProvider>
  </React.StrictMode>
);


I have tried everything and resorted to useAppContext even though I am very unfamiliar with react context.

2

Answers


  1. Chosen as BEST ANSWER

    So I actually figured out the best way to go about this is to use React Context. I set up two main states: mainMovies and watchlist, which can be accessible from anywhere.

    AppContext.js

    import React, { createContext, useContext, useState } from 'react';
    
    const AppContext = createContext();
    
    const AppProvider = ({ children }) => {
      const [mainMovies, setMainMovies] = useState([])
      const [watchlist, setWatchlist] = useState([]);
    
    
      const contextValue = {
        mainMovies,
        setMainMovies,
        watchlist,
        setWatchlist
      };
    
    
      return (
        <AppContext.Provider value={contextValue}>
          {children}
        </AppContext.Provider>
      );
    }
    
    const useAppContext = () => {
      const context = useContext(AppContext);
      if(!context) {
        throw new Error("useAppContext must be used within an AppProvider")
      }
    
      return context;
    }
    
    export { AppProvider, useAppContext}
    
    

    Watchlist.js

    
    const Watchlist = () => {
        const { watchlist } = useAppContext()
    
     
        return (
            <>
            
        <div className="main-container">
            <div className="main">
                <div className="movie-container-with-filter">
                    <div className="movie-container">
                        <div className="movies">
                        {watchlist && watchlist.length > 0 ? (
    
                            watchlist.map((movie) => (
                                 <Card
                                 key={movie.imdbID}
                                 movie={movie}
                                 
                                      />
                            ))
                        ) : (
                            <p>No movies in the watchlist</p>
                        )
                            
                        }
                        </div>
                        
                    </div>
    
    
    

    Card.js

    
    const Card = ({movie}) => {
        const { Title, Year, Actors, Genre, Image } = movie;
        const { setWatchlist } = useAppContext();
    
       
        const handleAddToWatchList = () => {
            setWatchlist((prevWatchlist) => [...prevWatchlist, movie])
        }
        
        
        return(
            <div className="card">
                <div className="card-image">
                    <img src={Image} alt={Title}/>
                </div>
    
                <div className="card-info">
                    <h2 className="movie-title">{Title}</h2>
                    <p className="movie-year">Year: {Year}</p>
                    <p className="movie-genre">Genre: {Genre}</p>
                    <p className="movie-actor">Actor: {Actors}</p>
                </div>
    
    
    
    
                <div className="movie-buttons">
                    <button ><span class="material-symbols-outlined">star</span></button>
                    <button onClick={handleAddToWatchList}><span className="material-symbols-outlined">add</span></button>
                </div>
            </div>
    
    )
    }
    
    
    

    Thanks X8inez for contributing to the thought process.


  2. In your Card component, handleAddToWatchList is supposed to be passed to to onClick as so onClick={handleAddToWatchList} and in Watchlist, this is how you call the function handleAddToWatchList={() => handleAddToWatchList(movie)}

    The bug occurs because handleAddToWatchList is a required prop in the Card component and it’s not used in the Home component.

    You can pass in an empty function to make handleAddToWatchList optional since it isn’t used in the Home component.

    
    const Card = ({movie, handleAddToWatchList}) => {
        const { Title, Year, Actors, Genre, Image } = movie;
    
       
        const movieInfo = (event) => {
            event.preventDefault();
            console.log(movie);
        }
        
        
        
        return(
            <div className="card">
                <div className="card-image">
                    <img src={Image} alt="interstellar"/>
                </div>
    
                <div className="card-info">
                    <h2 className="movie-title">{Title}</h2>
                    <p className="movie-year">Year: {Year}</p>
                    <p className="movie-genre">Genre: {Genre}</p>
                    <p className="movie-actor">Actor: {Actors}</p>
                </div>
    
    
    
    
                <div className="movie-buttons">
                    <button ><span class="material-symbols-outlined">star</span></button>
                    <button onClick={handleAddToWatchList}><span className="material-symbols-outlined">add</span></button>
                </div>
            </div>
    
    )
    }
    
    
    export default Card;
    
    const Watchlist = () => {
        const { watchlistData, setWatchlist } = useAppContext();
    
        const handleAddToWatchList = (movie) => {
    
            if (!watchlistData.some((item) => item.id === movie.imdbID)) {
                setWatchlist((prevData) => [...prevData, movie]);
              } else {
                console.log(`${movie.title} is already in the watchlist.`);
              }
        }
    
    
    
        return (
            <>
            <nav>
                <ul>
                    <li> <Link to="/" style={linkStyle}> Log Out </Link> </li>
                    <li> <Link to="/reviews" style={linkStyle}>My Reviews</Link>  </li>
                    <li> <Link to="/home" style={linkStyle}> Home </Link></li>
                </ul>
            </nav>
        <div className="main-container">
            <div className="main">
                <div className="movie-container-with-filter">
                    <div className="movie-container">
                        {watchlistData && watchlistData.length > 0 ? (
    
                            watchlistData.map((movie) => (
                                 <Card
                                 key={movie.imdbID}
                                 movie={movie}
                                 handleAddToWatchList={() => handleAddToWatchList(movie)}
                                      />
                            ))
                        ) : (
                            <p>No movies in the watchlist</p>
                        )
                            
                        }
                    </div>
                </div>
           </div>
        </div
    )
    

    In your Home component, add handleAddToWatchList={() => {}} to your Card component as a prop

    <Card 
      key={movie.imdbID}
      movie={{
           mage:movie.Poster,
           Title:movie.Title,
           Year:movie.Year,
           Actors:movie.Actors,
           Genre:movie.Genre
      }}
      handleAddToWatchList={() => {}}
    />
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search