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>
</>
);
}
(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>
);
}
/* ------------------------------------------------------ */
e.g. :
(Last Lecture i.e Lecture 18)
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 (
<>
</>
);
}
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
useState
Here in the example the rating was 8.6 which is greater than 8.
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. isTop
state is not re calculated on the second re render.useEffect
hook, as seen in the images above. 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
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])
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 :
Right Method (using callbacks) :
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]
);