components
- App.js – The parent of the other three components: contains functions that handle API requests, and contains functions that call the API during the initial rendering of the component.
- Header.js – Renders the application title and accepts the title property.
- Movie.js – Render each Movie.
- Search.js – contains a form with an input element and a Search button, a function that handles the input element and resets the field, and a function that calls the Search function, passed to it as props.
steps
1. Configure the React application
npx create-react-app movie-search
Copy the code
2. Set components
1) Create a folder “components” under SRC
Contains the four components described in Component Composition.
2) Update the import in index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App'; // this changed
ReactDOM.render(<App />.document.getElementById('root'));
Copy the code
3) Set header. js and export it
Add the following code to header. js to create a component that renders the Header tag with the text property.
import React from "react";
const Header = (props) = > {
return (
<header className="App-header">
<h2>{props.text}</h2>
</header>
);
};
export default Header;
Copy the code
4) Update app.css
The following code contains all of the project styles
.App {
text-align: center;
}
/* Title style */
.App-header {
/* Flex layout */
background-color: #282c34;
height: 70px;
display: flex;
flex-direction: column;
justify-self: center;
align-items: center;
/* vmin: the smaller percentage of the current window width and height. 1vw means that the window width is 1% so that the text size is the same across the screen */
font-size: calc(10px + 2vmin);
color: white;
padding: 20px;
cursor: pointer;
}
.loading {
display: block;
width: 20%;
margin: auto;
}
.App-intro {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans"."Helvetica Neue", sans-serif;
font-size: calc(10px + 2vmin);
}
/* new css for movie component */
* {
/* Width and height attributes include content, inner margins, and borders, but not margins */
box-sizing: border-box;
}
.movies {
display: flex;
flex-wrap: wrap;
flex-direction: row;
}
.movies h2 {
display: inline-block;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans"."Helvetica Neue", sans-serif;
height: 45px;
width: 200px;
font-size: medium;
}
.App-header h2 {
margin: 0;
}
.movie {
padding: 5px 25px 10px 25px;
/* max-width overwrites width, but min-width overwrites max-width */
/* The maximum width of the parent block-level container is
. */
max-width: 25%;
}
.errorMessage {
margin: auto;
font-weight: bold;
color: rgb(161.15.15);
}
.search {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
margin-top: 10px;
}
.input[type="submit"] {
padding: 5px;
background-color: transparent;
color: black;
border: 1px solid black;
width: 80px;
margin-left: 5px;
cursor: pointer;
}
.input[type="submit"]:hover {
background-color: #282c34;
color: lightskyblue;
}
.search > input[type="text"] {
width: 40%;
min-width: 170px;
}
@media screen and (min-width: 694px) and (max-width: 915px) {
.movie {
max-width: 33%;
}
}
@media screen and (min-width: 652px) and (max-width: 693px) {
.movie {
max-width: 50%;
}
}
@media screen and (max-width: 651px) {
.movie {
max-width: 100%; margin: auto; }}Copy the code
5) Set up the movie.js component and export it
A presentation component that renders the movie title, image, and year, without any internal state.
Set DEFAULT_PLACEHOLDER_IMAGE to provide placeholder images for movies retrieved from the API that have no images.
import React from "react";
const DEFAULT_PLACEHOLDER_IMAGE =
"https://m.media-amazon.com/images/M/MV5BMTczNTI2ODUwOF5BMl5BanBnXkFtZTcwMTU0NTIzMw@@._V1_SX300.jpg";
const Movie = ({ movie }) = > {
const poster =
movie.Poster === "N/A" ? DEFAULT_PLACEHOLDER_IMAGE : movie.Poster;
return (
<div className="movie">
<h2>{movie.Title}</h2>
<div>
<img
width="200"
alt={`The movie titled:The ${movie.Title} `}src={poster}
/>
</div>
<p>({movie.Year})</p>
</div>
);
};
export default Movie;
Copy the code
6) Set up the search.js component (stateful) and export it
Using the useState API, it lets you add React state to function components. It takes the initial state as an argument and returns an array containing the current state and a function to update the state.
The INPUT field is passed in as the current state. When the onChange event occurs, the handleSearchInputChanges function is called, which calls the function returned by useState to update the status value to the current state.
The resetInputField function is then called, which calls the function returned by useState to update the status value to an empty string, thus clearing the input field.
import React, { useState } from 'react';
const Search = ({ search }) = > {
//input Indicates the status of the input
const [searchValue, setSearchValue] = useState(' ');
// Update the status value to the current state of the input
const handleSearchInputChanges = (e) = > {
setSearchValue(e.target.value);
}
// Reset the field
const resetInputField = () = > {
setSearchValue(' ');
}
// contains the function that calls the search function, which is passed to it as props
const callSearchFunction = (e) = > {
e.preventDefault();
search(searchValue);
resetInputField();
}
// contains a form with input elements that search for button
return (
<form className="search">
<input
onChange={handleSearchInputChanges}
type="text"
value={searchValue}
/>
<input
onClick={callSearchFunction}
type="submit"
value="SEARCH"
/>
</form>
);
}
export default Search;
Copy the code
7) Update the app.js component
Pass in the style and three child components
import React, { useState, useEffect } from 'react';
import '.. /App.css';
import Header from './Header';
import Movie from './Movie';
import Search from './Search';
Copy the code
Sets the array of movies displayed on the initial page.
const MOVIE_API_URL = "https://www.omdbapi.com/?s=princess&apikey=4a3b711b";
Copy the code
Define App functions:
Set up the three useState functions to handle the load state, get the movie array from the server, and handle errors that may be encountered with API requests.
Use the useEffect function to get data once from MOVIE_API_URL while the component is mounted.
Define the search function that updates the status value each time a new field is searched and passes in the new field searchValue entered by the search behavior to initiate an HTTP request using FETCH. As follows:
const App = () = > {
// Handle the load state
const [loading, setLoading] = useState(true);
// Get the movie array from the server
const [movies, setMovies] = useState([]);
// Handle any errors that may occur while making API requests
const [errorMessage, setErrorMessage] = useState(null);
// Call the API during the initial rendering of the component
Use the useEffect function to isolate side effects
useEffect(() = > {
fetch(MOVIE_API_URL)
.then(res= > res.json())
.then(result= > {
setMovies(result.Search);
setLoading(false); }); } []);const search = searchValue= > {
setLoading(true);
setErrorMessage(null);
// Process API requests
fetch(`https://www.omdbapi.com/?s=${searchValue}&apikey=4a3b711b`)
.then(res= > res.json())
.then(result= > {
if (result.Response === "True") {
setMovies(result.Search);
setLoading(false);
} else {
setErrorMessage(result.error);
setLoading(false); }}); };const { movies, errorMessage, loading } = state;
return (
<div className="App">
<div className="m-container">
<Header text="HOOKED" />
<Search search={search} />
<p className="App-intro">
Sharing a few of our favourite movies
</p>
<div className="movies">{loading && ! errorMessage ? (<span>loading</span>
) : errorMessage ? (
<div className="errorMessage">{errorMessage}</div>
) : (
movies.map((movie, index) => (
<Movie key={` ${index}-The ${movie.Title} `}movie={movie} />)))}</div>
</div>
</div>
);
};
Copy the code
[Optimization] Can get show movie array wrapped in a function. As follows:
constretrivedMovies = loading && ! errorMessage ? (<span>loading</span>
) : errorMessage ? (
<div className="errorMessage">{errorMessage}</div>
) : (
movies.map((movie, index) = > (
<Movie key={` ${index}-The ${movie.Title} `}movie={movie} />)))return (
<div className="App">
<div className="m-container">
<Header text="HOOKED" />
<Search search={search} />
<p className="App-intro">Sharing a few of our favourite movies</p>
<div className="movies">{retrivedMovies}</div>
</div>
</div>
);
Copy the code
Since we use three useState functions that are related to each other, we can consider using the useReducer function instead. Stringing of data in AXIos is done automatically, so consider changing the way data is fetched from FETCH to Axios.
Add giFs to load images.
import { initialState, reducer } from '.. /store/reducer';
import spinner from '.. /image/giphy.gif';
import axios from 'axios';
Copy the code
const initialState = {
loading: true.movies: [].errorMessage: null
};
const reducer = (state, action) = > {
switch (action.type) {
case "SEARCH_MOVIES_REQUEST":
return {
...state,
loading: true.errorMessage: null
};
case "SEARCH_MOVIES_SUCCESS":
return {
...state,
loading: false.movies: action.payload
};
case "SEARCH_MOVIES_FAILURE":
return {
...state,
loading: false.errorMessage: action.error
};
default:
returnstate; }};const App = () = > {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() = > {
axios.get(MOVIE_API_URL).then(result= > {
dispatch({
type: "SEARCH_MOVIES_SUCCESS".payload: result.data.Search }); }); } []);const search = searchValue= > {
dispatch({
type: "SEARCH_MOVIES_REQUEST"
});
axios(`https://www.omdbapi.com/?s=${searchValue}&apikey=4a3b711b`)
.then(result= > {
if (result.data.Response === "True") {
dispatch({
type: "SEARCH_MOVIES_SUCCESS".payload: result.data.Search
});
} else {
dispatch({
type: "SEARCH_MOVIES_FAILURE".error: result.data.Error }); }}); };const { movies, errorMessage, loading } = state;
constretrivedMovies = loading && ! errorMessage ? (<img className="loading" src={spinner} alt="Loading spinner" />
) : errorMessage ? (
<div className="errorMessage">{errorMessage}</div>
) : (
movies.map((movie, index) = > (
<Movie key={` ${index}-The ${movie.Title} `}movie={movie} />)))return (
<div className="App">
<div className="m-container">
<Header text="HOOKED" />
<Search search={search} />
<p className="App-intro">Sharing a few of our favourite movies</p>
<div className="movies">{retrivedMovies}</div>
</div>
</div>
);
};
export default App;
Copy the code
8) Encapsulate the Reducer function
Create a Store folder under SRC, create a Reducer folder, and create the index.js file.
Cut the following code into a file and export it:
export const initialState = {
loading: true.movies: [].errorMessage: null
};
export const reducer = (state, action) = > {
switch (action.type) {
case "SEARCH_MOVIES_REQUEST":
return {
...state,
loading: true.errorMessage: null
};
case "SEARCH_MOVIES_SUCCESS":
return {
...state,
loading: false.movies: action.payload
};
case "SEARCH_MOVIES_FAILURE":
return {
...state,
loading: false.errorMessage: action.error
};
default:
returnstate; }};Copy the code
How to build a movie Search app using React Hooks