Today we will create a simple react app with the help of OMDB API requests. This app should compatible with search movies, series, and episodes by using the OMDB API and the detail feature by clicking on a particular movie/series. First, we need to obtain the API Key to use the OMDB request endpoints on their website which is completely free but with certain limits.
We will use the Ant Design to quickly set up the user interface section. It has many built-in components that save a lot of time. Let’s start the coding part without any further delay.
npx create-react-app omdb-app
The next step is to add the user interface dependencies, the Ant Design. Move to the working directory with cd omdb-app
and install the dependencies.
npm i antd
We are going to use the Layout, Input, Row, Col etc. component to create the UI as displayed in the screenshot. Let’s open the App.js file and start adding the dependencies.
import React, { useEffect, useState } from 'react';
import {
Layout,
Input,
Row,
Col,
Card,
Tag,
Spin,
Alert,
Modal,
Typography
} from 'antd';
import 'antd/dist/antd.css';
const API_KEY = 'cexxxxxxx6';
const { Header, Content, Footer } = Layout;
const { Search } = Input;
const { Meta } = Card;
const TextTitle = Typography.Title;
We need to implement 3 sections. First for Search, Second for Content that holds the cards for movies/series image and name, And the Third is the Modal that displays the ratings, detail, runtime, genre etc.
The next step is to create a basic layout that holds the Header, Content and the Footer section.
function App() {
return (
<div className="App">
<Layout className="layout">
<Header>
<div style={{ textAlign: 'center'}}>
<TextTitle style={{color: '#ffffff', marginTop: '14px'}} level={3}>OMDB API + React</TextTitle>
</div>
</Header>
<Content style={{ padding: '0 50px' }}>
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
...
...
...
...
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>OMDB Movies ©2019</Footer>
</Layout>
</div>
);
}
export default App;
Search Container
The next step is to create the SearchBox component that holds the UI and Event handling part.
const SearchBox = () => {
return (
<Row>
<Col span={12} offset={6}>
<Search
placeholder="enter movie, series, episode name"
enterButton="Search"
size="large"
onSearch={value => console.log(value)}
/>
</Col>
</Row>
)
}
Don’t get confused with the <Search /> component, It is borrowed from the Ant Design. The Search component is a part of the Input Component that we already imported at the top of our App.js file const { Search } = Input;
Content Container
The next step is to create a component that holds the Image and Title of the movie/series.
const ColCardBox = () => {
return (
<Col style={{margin: '20px 0'}} className="gutter-row" span={4}>
<div className="gutter-box">
<Card
style={{ width: 200 }}
cover={
<img
alt=""
src="https://placehold.it/198x264&text=Image+Not+Found"
/>
}
>
<Meta
title="Title Goes Here"
description={false}
/>
<Row style={{marginTop: '10px'}} className="gutter-row">
<Col>
<Tag color="magenta">Movie / Series</Tag>
</Col>
</Row>
</Card>
</div>
</Col>
)
}
All this is just a user interface part, we have used the Row, Col, Card, Meta components by Ant Design.
The next step is to fit both the SearchBox and the ColCardBox component to our app.
function App() {
return (
<div className="App">
<Layout className="layout">
<Header>
....
</Header>
<Content style={{ padding: '0 50px' }}>
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
<SearchBox />
<br />
<Row gutter={16} type="flex" justify="center">
<ColCardBox />
</Row>
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>OMDB Movies ©2019</Footer>
</Layout>
</div>
);
}
export default App;
The next step is to call the OMDB API and display some default results. We are going to use the Effect and State Hook to achieve that.
Tip
If you’re familiar with React class lifecycle methods, you can think of useEffect
Hook as componentDidMount
, componentDidUpdate
, and componentWillUnmount
combined.
Let’s define 4 states for data, error, loading and q. The data state should null by default and will hold the response object later. The error state should also be null and will update once we get the error. The loading state to display the spinner and the q state will hold the searched query parameter. We have set the batman as a default search parameter.
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [q, setQuery] = useState('batman');
The next step is to call the OMDB API with the help of the Effect hook or we can call it the side effect.
useEffect(() => {
setLoading(true);
setError(null);
setData(null);
fetch(`http://www.omdbapi.com/?s=${q}&apikey=${API_KEY}`)
.then(resp => resp)
.then(resp => resp.json())
.then(response => {
if (response.Response === 'False') {
setError(response.Error);
}
else {
setData(response.Search);
}
setLoading(false);
})
.catch(({message}) => {
setError(message);
setLoading(false);
})
}, [q]);
As you can see, we have injected the defined q
state with the useEffect
hook and used the q
state with OMDB API. Let’s implement it with the user interface.
function App() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [q, setQuery] = useState('batman');
useEffect(() => {
setLoading(true);
setError(null);
setData(null);
fetch(`http://www.omdbapi.com/?s=${q}&apikey=${API_KEY}`)
.then(resp => resp)
.then(resp => resp.json())
.then(response => {
if (response.Response === 'False') {
setError(response.Error);
}
else {
setData(response.Search);
}
setLoading(false);
})
.catch(({message}) => {
setError(message);
setLoading(false);
})
}, [q]);
return (
<div className="App">
<Layout className="layout">
<Header>
....
</Header>
<Content style={{ padding: '0 50px' }}>
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
<SearchBox />
<br />
<Row gutter={16} type="flex" justify="center">
{ loading &&
<Loader />
}
{ error !== null &&
<div style={{margin: '20px 0'}}>
<Alert message={error} type="error" />
</div>
}
{ data !== null && data.length > 0 && data.map((result, index) => (
<ColCardBox
{...result}
/>
))}
</Row>
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>OMDB Movies ©2019</Footer>
</Layout>
</div>
);
}
Now we have to modify our ColCardBox component to display the data like Title, Poster and Type.
const ColCardBox = ({Title, Poster, Type}) => {
return (
<Col style={{margin: '20px 0'}} className="gutter-row" span={4}>
<div className="gutter-box">
<Card
style={{ width: 200 }}
cover={
<img
alt={Title}
src={Poster === 'N/A' ? 'https://placehold.it/198x264&text=Image+Not+Found' : Poster}
/>
}
>
<Meta
title={Title}
description={false}
/>
<Row style={{marginTop: '10px'}} className="gutter-row">
<Col>
<Tag color="magenta">{Type}</Tag>
</Col>
</Row>
</Card>
</div>
</Col>
)
}
Now you will get the result that matches the string batman. Because batman is the default search parameter. The next step is to attach the SearchBox event. We only update the search query parameter because of the useEffect
which is identical to componentDidUpdate
. We have to update our SearchBox component slightly to make it work.
const SearchBox = ({searchHandler}) => {
return (
<Row>
<Col span={12} offset={6}>
<Search
placeholder="enter movie, series, episode name"
enterButton="Search"
size="large"
onSearch={value => searchHandler(value)}
/>
</Col>
</Row>
)
}
<SearchBox searchHandler={setQuery} />
As you can see, we have attached the setQuery
event with searchHandler
property in SearchBox component.
Now if you try to search for something and hit enter or press the Search Button, then it will return the updated result.
Implement Modal Box
The next step is to implement the Modal box when we click on any of the Card. It is very easy to handle by adding three more states.
const [activateModal, setActivateModal] = useState(false);
const [detail, setShowDetail] = useState(false);
const [detailRequest, setDetailRequest] = useState(false);
The activateModal
state helps to close the Modal Component. The detail
state is used to collect the data. and the detailRequest
state is used to display the loader.
First, we will attach the Modal component and later will create a MovieDetail component that displays the returned details.
<Content style={{ padding: '0 50px' }}>
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
<SearchBox searchHandler={setQuery} />
<br />
<Row gutter={16} type="flex" justify="center">
...
...
...
</Row>
</div>
<Modal
title='Detail'
centered
visible={activateModal}
onCancel={() => setActivateModal(false)}
footer={null}
width={800}
>
{ detailRequest === false ?
(<MovieDetail {...detail} />) :
(<Loader />)
}
</Modal>
</Content>
Now let’s create the MovieDetail component.
const MovieDetail = ({Title, Poster, imdbRating, Rated, Runtime, Genre, Plot}) => {
return (
<Row>
<Col span={11}>
<img
src={Poster === 'N/A' ? 'https://placehold.it/198x264&text=Image+Not+Found' : Poster}
alt={Title}
/>
</Col>
<Col span={13}>
<Row>
<Col span={21}>
<TextTitle level={4}>{Title}</TextTitle></Col>
<Col span={3} style={{textAlign:'right'}}>
<TextTitle level={4}><span style={{color: '#41A8F8'}}>{imdbRating}</span></TextTitle>
</Col>
</Row>
<Row style={{marginBottom: '20px'}}>
<Col>
<Tag>{Rated}</Tag>
<Tag>{Runtime}</Tag>
<Tag>{Genre}</Tag>
</Col>
</Row>
<Row>
<Col>{Plot}</Col>
</Row>
</Col>
</Row>
)
}
Now we have to implement the click event to the card and another request to collect that particular movie/series data. The next step is to modify the ColCardBox component.
First, implement the newly defined state handler to the ColCardBox.
{ data !== null && data.length > 0 && data.map((result, index) => (
<ColCardBox
ShowDetail={setShowDetail}
DetailRequest={setDetailRequest}
ActivateModal={setActivateModal}
key={index}
{...result}
/>
))}
Now modify the ColCardBox component.
const ColCardBox = ({Title, imdbID, Poster, Type, ShowDetail, DetailRequest, ActivateModal}) => {
const clickHandler = () => {
// Display Modal and Loading Icon
ActivateModal(true);
DetailRequest(true);
fetch(`http://www.omdbapi.com/?i=${imdbID}&apikey=${API_KEY}`)
.then(resp => resp)
.then(resp => resp.json())
.then(response => {
DetailRequest(false);
ShowDetail(response);
})
.catch(({message}) => {
DetailRequest(false);
})
}
return (
<Col style={{margin: '20px 0'}} className="gutter-row" span={4}>
<div className="gutter-box">
<Card
style={{ width: 200 }}
cover={
<img
alt={Title}
src={Poster === 'N/A' ? 'https://placehold.it/198x264&text=Image+Not+Found' : Poster}
/>
}
onClick={() => clickHandler()}
>
<Meta
title={Title}
description={false}
/>
<Row style={{marginTop: '10px'}} className="gutter-row">
<Col>
<Tag color="magenta">{Type}</Tag>
</Col>
</Row>
</Card>
</div>
</Col>
)
}
We have added the onClick={() => clickHandler()}
on the Card component. The clickHandler
event will take send a request and grab the detail of a particular movie/series.
If you are facing any issue to use this example, please don’t hesitate to contact me through the comment section.