Explicitly and Implicitly Passing Props :

Notice this piece of code below

export default function App() {
  const [movies, setMovies] = useState(tempMovieData);

  return (
    <>
      <NavBar>
        {/* Notice that this NavBar is a function and not an HTML component*/}
        <Search />
        <NumResults movies={movies} />
      </NavBar>

      <Main >
      {/* Explicitly passing Elements as Props: */}
        <ListBox children={<MovieList movies={movies}/>}/>

      {/* Implicitly passing Elements: */}
        {/* <ListBox>
          <MovieList movies={movies}/>
        </ListBox> */}
        <WatchedBox />
      </Main>
      
    </>
  );
}

Entire Code :

(Above code is a part of the following code) :

import { useState } from "react";

const tempMovieData = [
  {
    imdbID: "tt1375666",
    Title: "Inception",
    Year: "2010",
    Poster:
      "https://m.media-amazon.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg",
  },
  {
    imdbID: "tt0133093",
    Title: "The Matrix",
    Year: "1999",
    Poster:
      "https://m.media-amazon.com/images/M/MV5BNzQzOTk3OTAtNDQ0Zi00ZTVkLWI0MTEtMDllZjNkYzNjNTc4L2ltYWdlXkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg",
  },
  {
    imdbID: "tt6751668",
    Title: "Parasite",
    Year: "2019",
    Poster:
      "https://m.media-amazon.com/images/M/MV5BYWZjMjk3ZTItODQ2ZC00NTY5LWE0ZDYtZTI3MjcwN2Q5NTVkXkEyXkFqcGdeQXVyODk4OTc3MTY@._V1_SX300.jpg",
  },
];

const tempWatchedData = [
  {
    imdbID: "tt1375666",
    Title: "Inception",
    Year: "2010",
    Poster:
      "https://m.media-amazon.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg",
    runtime: 148,
    imdbRating: 8.8,
    userRating: 10,
  },
  {
    imdbID: "tt0088763",
    Title: "Back to the Future",
    Year: "1985",
    Poster:
      "https://m.media-amazon.com/images/M/MV5BZmU0M2Y1OGUtZjIxNi00ZjBkLTg1MjgtOWIyNThiZWIwYjRiXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg",
    runtime: 116,
    imdbRating: 8.5,
    userRating: 9,
  },
];

const average = (arr) =>
  arr.reduce((acc, cur, i, arr) => acc + cur / arr.length, 0);

export default function App() {
  const [movies, setMovies] = useState(tempMovieData);

  return (
    <>
      <NavBar>
        {/* Notice that this NavBar is a function and not an HTML component*/}
        <Search />
        <NumResults movies={movies} />
      </NavBar>

      <Main >
      {/* Explicitly passing Elements as Props: */}
        <ListBox children={<MovieList movies={movies}/>}/>

      {/* Implicitly passing Elements: */}
        {/* <ListBox>
          <MovieList movies={movies}/>
        </ListBox> */}
        <WatchedBox />
      </Main>
      
    </>
  );
}
/* ----------------------- NavBar ----------------------- */
function NavBar({ children }) {
  return (
    <nav className="nav-bar">
      <Logo />
      {children}
    </nav>
  );
}

function Logo() {
  return (
    <div className="logo">
      <span role="img">🍿</span>
      <h1>usePopcorn</h1>
    </div>
  );
}

function Search() {
  const [query, setQuery] = useState("");

  return (
    <input
      className="search"
      type="text"
      placeholder="Search movies..."
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  );
}

function NumResults({ movies }) {
  return (
    <p className="num-results">
      Found <strong>{movies.length}</strong> results
    </p>
  );
}
/* ------------------------------------------------------ */

function Main({ children }) {
  return <main className="main">{children}</main>;
}

/* ----------------------- ListBox ---------------------- */
function ListBox({ children, element }) {
  const [isOpen1, setIsOpen1] = useState(true);

  return (
    <div className="box">
      <button
        className="btn-toggle"
        onClick={() => setIsOpen1((open) => !open)}
      >
        {isOpen1 ? "–" : "+"}
      </button>
      {isOpen1 && children}
    </div>
  );
}

function MovieList({movies}) {
  return (
    <ul className="list">
      {movies?.map((movie) => (
        <Movie movie={movie} key={movie.imdbID} />
      ))}
    </ul>
  );
}

function Movie({ movie }) {
  return (
    <li>
      <img src={movie.Poster} alt={`${movie.Title} poster`} />
      <h3>{movie.Title}</h3>
      <div>
        <p>
          <span>🗓</span>
          <span>{movie.Year}</span>
        </p>
      </div>
    </li>
  );
}
/* ------------------------------------------------------ */

/* --------------------- WatchedBox --------------------- */
function WatchedBox() {
  const [watched, setWatched] = useState(tempWatchedData);
  const [isOpen2, setIsOpen2] = useState(true);

  return (
    <div className="box">
      <button
        className="btn-toggle"
        onClick={() => setIsOpen2((open) => !open)}
      >
        {isOpen2 ? "–" : "+"}
      </button>
      {isOpen2 && (
        <>
          <WatchedSummary watched={watched} />
          <WatchedMoviesList watched={watched} />
        </>
      )}
    </div>
  );
}

function WatchedSummary({ watched }) {
  const avgImdbRating = average(watched.map((movie) => movie.imdbRating));
  const avgUserRating = average(watched.map((movie) => movie.userRating));
  const avgRuntime = average(watched.map((movie) => movie.runtime));

  return (
    <div className="summary">
      <h2>Movies you watched</h2>
      <div>
        <p>
          <span>#️⃣</span>
          <span>{watched.length} movies</span>
        </p>
        <p>
          <span>⭐️</span>
          <span>{avgImdbRating}</span>
        </p>
        <p>
          <span>🌟</span>
          <span>{avgUserRating}</span>
        </p>
        <p>
          <span></span>
          <span>{avgRuntime} min</span>
        </p>
      </div>
    </div>
  );
}

function WatchedMoviesList({ watched }) {
  return (
    <ul className="list">
      {watched.map((movie) => (
        <WatchedMovie movie={movie} key={movie.imdbID} />
      ))}
    </ul>
  );
}

function WatchedMovie({ movie }) {
  return (
    <li>
      <img src={movie.Poster} alt={`${movie.Title} poster`} />
      <h3>{movie.Title}</h3>
      <div>
        <p>
          <span>⭐️</span>
          <span>{movie.imdbRating}</span>
        </p>
        <p>
          <span>🌟</span>
          <span>{movie.userRating}</span>
        </p>
        <p>
          <span></span>
          <span>{movie.runtime} min</span>
        </p>
      </div>
    </li>
  );
}
/* ------------------------------------------------------ */

Virtual Dom (React Element Tree) :

Pasted image 20231231133742.png

The Reconciler : Fiber

Pasted image 20231231140650.png

Key Props :

Pasted image 20231231161907.png

Pasted image 20231231162040.png
Pasted image 20231231163524.png
Pasted image 20231231163549.png

How updates are batched :

Pasted image 20231231171923.png
Pasted image 20231231172659.png

Pasted image 20231231173428.png

If we need to update state based on previous update :

Pasted image 20231231173359.png
e.g. :
Pasted image 20231231175029.png

SUMMARY of : 11 - How React Works Behind the Scenes

(Last Lecture i.e Lecture 18)
Pasted image 20240103015706.png

Pasted image 20240103020844.png

  • Render Logic is not allowed to produce any side effects, such as API Calls (see last point in the image above). Why is this ? To prevent infinite loop of state setting and re-rendering. For e.g. :
const KEY = 'f4feb5fa';

export default function App() {
  const [movies, setMovies] = useState(tempMovieData);

	// here this fetch will set a state, setting a state calls the App() function again to re-render, then as we go past the fetch function, the data is fetch again and the state is changed again, thus resulting in an infinite loop of state setting and re-rendering
  fetch(`http://www.omdbapi.com/?apikey=${KEY}&s=interstellar&`).then(res => res.json()).then(data => setMovies(data.Search));

  return (
    <>
    </>
  );
}
  • ! Hence, setState(); should not be used inside a render logic.

Pasted image 20240103021131.png

Use Effect :

useEffect(function()
  {
  fetch(`http://www.omdbapi.com/?apikey=${KEY}&s=interstellar&`).then(res => res.json()).then(data => setMovies(data.Search));
  }, []); // here the empty dependency array "[]" which means that the effect that we just specified here will only run on mount i.e. it will only run when this App() component is rendered for the very first time
  // register effect basically means that we want this fetch code to be executed after the App() component has finished rendering on the browser

Pasted image 20240108171124.png
Pasted image 20240108205948.png
Pasted image 20240108211358.png

  • If an effect sets state, an additional render will be required.

State Calculation + useState

Pasted image 20240302232547.png
Here in the example the rating was 8.6 which is greater than 8.
Pasted image 20240302232642.png

  • But we still get false as the output. This is because, the initial state will be set only when the component first mounts. When the component first mounts, the imdbRating will still be undefined, hence we get the false value.
  • On the second render this state will remain the same since we have not updated the value of it anywhere and isTop state is not re calculated on the second re render.
  • To get past through this problem we can use the useEffect hook, as seen in the images above.

Fetch API CleanUp / Cancelling Requests :

  const controller = new AbortController(); // this is a browser API, it's got nothing to do with React
  useEffect(() => {
    async function fetchMovies() {
      try {
        setIsLoading(true);
        setError("");


        const res = await fetch(
          `http://www.omdbapi.com/?apikey=${KEY}&s=${query}&`, {signal: controller.signal}
        );

        if (!res.ok)
          throw new Error("Something went wrong with fetching movies");

        const data = await res.json();

        if (data.Response === "False")
          // as API responds with {Response: 'False} when no search result is generated
          throw new Error("Movie not found");

        setMovies(data.Search);
        setIsLoading(false);
      } catch (err) 
      {
        if(err.name !== "AbortError") // we did this because when we cancel a fetch API request JS sees that as an error and then throws an error, we do not want this behaviour
        {
          setError(err.message);
        }
      } finally {
        setIsLoading(false);
      } // this 'finally' block will always execute
    }
    if (query.length <= 2) {
      setMovies([]);
      setError("");
      return;
    }
    fetchMovies();

    // cleanup function : we want to cancel the current request to the API each time the 
    return function () {
      controller.abort();
    }
  }, [query]); // here the empty dependency array "[]" which means that the effect that we just specified here will only run on mount i.e. it will only run when this App() component is rendered for the very first time
  // register effect basically means that we want this fetch code to be executed after the App() component has finished rendering on the browser

Escape Key / Keypress Event

useEffect(function() {
    function callback (e)
    {
      if(e.code === 'Escape')
      {
        onCloseMovie();
        console.log("CLOSING");
        
      }
    }
    document.addEventListener('keydown', callback); //adding an event listener
      
      // we'll have to remove the event listener every time the movie details component un mounts otherwise every time we'll select any other movie, it'll render the movie details component again which will add an extra event listener and will keep on doing so as we change the movies.
      return function() {
        document.removeEventListener('keydown', callback) // here the function that we pass in must be the same as the one we used in .addEventListener so we have to create a seperate function 'callback' for that.
      }
  }, [onCloseMovie])

Updating State using another state in a single go :

We'll use callback functions to perform the data updation. We do this because in React, state updates are inherently asynchronous processes. When you call the setState function, React schedules an update rather than immediately applying the changes.

Wrong Method : Pasted image 20240302235658.png
Right Method (using callbacks) : Pasted image 20240302235728.png

Adding Event Listeners :

  useEffect(
    function () {
      function callback(e) {
        if (e.code === "Escape") {
          onCloseMovie();
          console.log("CLOSING");
        }
      }
      document.addEventListener("keydown", callback); //adding an event listener

      // we'll have to remove the event listener every time the movie details component un mounts otherwise every time we'll select any other movie, it'll render the movie details component again which will add an extra event listener and will keep on doing so as we change the movies.
      return function () {
        document.removeEventListener("keydown", callback); // here the function that we pass in must be the same as the one we used in .addEventListener so we have to create a seperate function 'callback' for that.
      };
    },
    [onCloseMovie]
  );