Search code examples
javascriptreactjsreact-propssuperreact-component

ReactJS prop is not a function - do I need a class component instead of function and call super()?


I'm following this tutorial on YouTube https://youtu.be/b9eMGE7QtTk

The full code can be found here: https://gist.github.com/adrianhajdin/997a8cdf94234e889fa47be89a4759f1

The tutorial was great, but it didn't split all the functionalities into components which is React used for (or I'm so lead to believe).

So we have the App.js

import React, { useState, useEffect } from "react";

import MovieCard from "./MovieCard";
import SearchIcon from "./search.svg";
import "./App.css";

const API_URL = "http://www.omdbapi.com?apikey=b6003d8a";

const App = () => {
  const [searchTerm, setSearchTerm] = useState("");
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    searchMovies("Batman");
  }, []);

  const searchMovies = async (title) => {
    const response = await fetch(`${API_URL}&s=${title}`);
    const data = await response.json();

    setMovies(data.Search);
  };

  return (
    <div className="app">
      <h1>MovieLand</h1>

      <div className="search">
        <input
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          placeholder="Search for movies"
        />
        <img
          src={SearchIcon}
          alt="search"
          onClick={() => searchMovies(searchTerm)}
        />
      </div>

      {movies?.length > 0 ? (
        <div className="container">
          {movies.map((movie) => (
            <MovieCard movie={movie} />
          ))}
        </div>
      ) : (
        <div className="empty">
          <h2>No movies found</h2>
        </div>
      )}
    </div>
  );
};

export default App;

MovieCards.jsx is as follows:

import React from 'react';

const MovieCard = ({ movie: { imdbID, Year, Poster, Title, Type } }) => {
  return (
    <div className="movie" key={imdbID}>
      <div>
        <p>{Year}</p>
      </div>

      <div>
        <img src={Poster !== "N/A" ? Poster : "https://via.placeholder.com/400"} alt={Title} />
      </div>

      <div>
        <span>{Type}</span>
        <h3>{Title}</h3>
      </div>
    </div>
  );
}

export default MovieCard;

The app works, but I want to move className="search" to be its own component like Search /.

The code I end up having in App.js is

//at the top of App.jx
import Search from "./Search"

// in const App
<Search prop={searchMovies}/>

And in the new Seach / component

import { useState } from "react";
import SearchIcon from './search.svg';



const Search = ( prop ) => {

    const [searchTerm, setSearchTerm] = useState("");

    return (
        <div className="search">
            <input
                value={searchTerm}
                onChange={(e) => setSearchTerm(e.target.value)}
                placeholder="Search"
            />
            <img
                src={SearchIcon}
                alt="search"
                onClick={() => prop(searchTerm)}
                //props used to be searchMovies
            />
        </div>
    )
}

export default Search;

When typing something in the search field on the app and clicking on the search icon I get the following error:

prop is not a function

If my research has been correct, I need to use a constructor and super() But it seems like the constructor needs to be called in a class Search instead of const Search as it breaks the code. Is that the case or is there a way to use the constructor in a function component, or is there something else completely that I should do? Also, if there is a great tutorial you could recommend for super() I'd be really grateful.

Other thing that I want to do is to make a Results component or call it whatever that would have the {movies?.length > 0 ? ( part of the code, but I feel like that will be a different headache.

Basically what I want is to have:

const App = () => {
    return (
        <div className="app">
          <h1>Movie Site</h1>
          <Search />
          <Results />
        </div>
    );
};

Or as shown in the picture

enter image description here

Hope all this makes sense. Also, I want to preface that I do not expect anyone to write the code for me, but if it helps me understand this it's appreciated. YT tutorials are appreciated as well.


Solution

  • Okay, after a push in the right direction from jonrsharpe and renaming the props into random things I figured it out.

    As jonrsharpe said, my function is prop.prop, so if I wanted to call searchTerm in

    onClick={() => prop(searchTerm)}
    

    it should be

    onClick={() => prop.prop(searchTerm)}
    

    Now, that works, but looks silly. So renaming the first "prop" in prop.prop and the prop in const Search to searchOnClick leaves searchOnClick.prop(searchTerm) which still works. Great.

    Then in App.js renaming prop in Search prop={searchMovies} to searchOnClick={searchMovies} needs to be followed by renaming searchOnClick.prop in Search.jsx to searchOnClick.searchOnClick.

    Lastly, we want to destructure the props as jonrsharpe said.

    const Search = ( searchOnClick ) => {
    

    would become

    const Search = ( {searchOnClick} ) => {
    

    That allows us to remake searchOnClick.searchOnClick(searchTerm) to searchOnClick(searchTerm) only.

    The whole point is that the prop calls the whole componentName variable=value but it doesn't take the value of the variable automatically so it needs to be called like prop.variable until destructured where it can be called as variable only.

    Now that I figured this out it feels silly spending two days on this. Thanks to jonrsharpe again, and hope this helps to someone else in the future.