React throwaway app 2: Movie Search App
Tags: beginners, react, webdev
Published: 2018-07-04 14:01:39

---
title: React throwaway app 2: Movie Search App
published: true
description: A simple movie search app.
tags: #beginners, #react, #webdev
---

In the first [article](https://dev.to/fleepgeek/react-throwaway-app-1-currency-converter--52c7), I introduced you to the aim of the series and you built a currency converter. In this one, you would be building a movie search app.

## The Rules (just to remind you)
- Your app should be completed within 60 minutes (depending on the complexity).
- Must be pure React (no react-router or redux).
- Must delete the project after one week. Why? These are basic apps you should be able to build anytime and not worthy of showcasing as a portfolio for a serious job interview.
- Don't spend much time on designing. Remember, the idea is to check if you think in React. You could style to your taste after 60 minutes.
- Don't look at my solution until you have completed yours. Else, you would be struck with 5 years of 'tutorial purgatory'

##App 2 - Movie Search App
- Build a Movie App that connects to an external api.
- Search for movies movies and select one to display.
- Duration should be within 1 - 2 hours (including styling).

Here is a screenshot of what i expect you to build:

![movie screenshot](https://thepracticaldev.s3.amazonaws.com/i/rycx1mwmt1juinl0lhxj.png)

This app would show that you understand how:
- components and states work
- to request data from an api
- component life cycle methods
- to use events
- to update your UI based on state change

Your time starts now! Remember not to look at my solution until you're done with yours.

## My Solution
I used the [OMDb API](http://www.omdbapi.com/) to get my movie data. You have to get an api key (it is free). I must confess, i spent above 60 minutes to complete this because i had to get familiar with the api by playing around with different requests on PostMan. As always, i used create-react-app to generate my project.

To structure my app, i had to decide what would be containers and components.

![app structure](https://thepracticaldev.s3.amazonaws.com/i/nji5hcb8ujfrmt99p6mo.png)

Here is my folder structure:
![folder struture](https://thepracticaldev.s3.amazonaws.com/i/krwwqa72ph2fp1a7vjap.JPG)


**MovieCard.js:**

This component is used to display the selected movie. It receives its movie data via props.

```jsx
import React from 'react';

import './MovieCard.css';

const MovieCard = (props) => {
	return (
		<div className="container">
			<div className="movie-card">
				<div className="movie-header" style={{ backgroundImage: `url(${props.movie.Poster})` }}>
				</div>
				<div className="movie-content">
					<div className="movie-content-header">
						<h3 className="movie-title">{props.movie.Title}</h3>
					</div>
					<div className="movie-info">
						<div className="info-section">
							<label>Released</label>
							<span>{props.movie.Released}</span>
						</div>
						<div className="info-section">
							<label>IMDB Rating</label>
							<span>{props.movie.imdbRating}</span>
						</div>
						<div className="info-section">
							<label>Rated</label>
							<span>{props.movie.Rated}</span>
						</div>
						<div className="info-section">
							<label>Runtime</label>
							<span>{props.movie.Runtime}</span>
						</div>
					</div>
					<div className="plot" style={{fontSize: '12px'}}>
						<p>{props.movie.Plot}</p>
					</div>
				</div>
			</div>
		</div>
	);
};

export default MovieCard;
```


**MovieCard.css:**

```css

.container {
	display: flex;
	flex-wrap: wrap;
	max-width: 100%;
	margin-left: auto;
	margin-right: auto;
	justify-content: center;
}

.movie-card {
	background: #ffffff;
	box-shadow: 0px 6px 18px rgba(0,0,0,.1);
	width: 100%;
	max-width: 290px;
	margin: 2em;
	border-radius: 10px;
    display:inline-block;
    z-index: 10;
}

.movie-header {
	padding:0;
	margin: 0;
	height: 434px;
	width: 100%;
	display: block;
	border-top-left-radius: 10px;
    border-top-right-radius:10px;
    background-size: cover;
}

.movie-content {
	padding: 18px 18px 24px 18px;
	margin: 0;
}

.movie-content-header, .movie-info {
	display: table;
	width: 100%;
}

.movie-title {
	font-size: 24px;
	margin: 0;
    display: table-cell;
    cursor: pointer;
}

.movie-title:hover {
    color:rgb(228, 194, 42);
}

.movie-info {
	margin-top: 1em;
}

.info-section {
	display: table-cell;
	text-transform: uppercase;
	text-align:center;
}

.info-section:first-of-type {
	text-align:left;
}

.info-section:last-of-type {
	text-align:right;
}

.info-section label {
	display: block;
	color: rgba(0,0,0,.5);
	margin-bottom: .5em;
	font-size: 9px;
}

.info-section span {
	font-weight: 700;
	font-size: 11px;
}

@media only screen and (max-width: 400px) {
	.movie-header {
		height: 400px;
	}
}
```

**Search.js**

Next, we have the Search component which contains the search input and the returned list of result.
Here is the Search.js:

```jsx
import React from 'react';

import './Search.css';

const Search = (props) => {
    let resultList = null

    if (props.searching && (props.defaultTitle !== '')) {
        resultList = (
            <ul className="results">
                {props.results.map(item => (
                    <li key={item.imdbID} onClick={() => props.clicked(item)}>
                        <img src={item.Poster} alt="Movie Poster"/>
                        {item.Title}
                    </li>
                ))}
            </ul>
        )
    }

    return (
        <div className="search">
            <input type="search" name="movie-search" value={props.defaultTitle} onChange={props.search} />
            {resultList}
        </div>
    );
};

export default Search;

```

**Search.css**

```css
.search {
    position: relative;
    margin: 0 auto;
    width: 300px;
    margin-top: 10px;
}

.search input {
    height: 26px;
    width: 100%;
    padding: 0 12px 0 25px;
    background: white;
    border: 1px solid #babdcc;
    border-radius: 13px;
    box-sizing: border-box;
    box-shadow: inset 0 1px #e5e7ed, 0 1px 0 #fcfcfc;
}

.search input:focus {
    outline: none;
    border-color: #66b1ee;
    box-shadow: 0 0 2px rgba(85, 168, 236, 0.9);
}


.search .results {
    display: block;
    position: absolute;
    top: 35px;
    left: 0;
    right: 0;
    z-index: 20;
    padding: 0;
    margin: 0;
    border-width: 1px;
    border-style: solid;
    border-color: #cbcfe2 #c8cee7 #c4c7d7;
    border-radius: 3px;
    background-color: #fdfdfd;
}

.search .results li { 
    display: flex;
    align-items: center;
    padding: 5px;
    border-bottom: 1px solid rgba(88, 85, 85, 0.3);
    text-align: left;
    height: 50px;
    cursor: pointer;
}

.search .results li img { 
    width: 30px;
    margin-right: 5px;
}

.search .results li:hover { 
    background: rgba(88, 85, 85, 0.1);
}
```

**MovieSearch.js**

I made MovieSearch to be a stateful component because i want to manage all my states there and pass the data to other components via props. First, make sure you get your api key from omdb api.
Here is my MovieSearch.js container:

```jsx
import React, { Component } from 'react';
import axios from 'axios';

import MovieCard from '../../components/MovieCard/MovieCard';
import Search from '../../components/Search/Search';

class MovieSearch extends Component {
    state = {
        movieId: 'tt1442449', // default imdb id (Spartacus)
        title: '',
        movie: {},
        searchResults: [],
        isSearching: false,
    }

    componentDidMount() {
        this.loadMovie()
    }

    componentDidUpdate(prevProps, prevState) {
        if (prevState.movieId !== this.state.movieId) {
            this.loadMovie()
        }
    }
    
    loadMovie() {
        axios.get(`http://www.omdbapi.com/?apikey=YOUR_API_KEY&i=${this.state.movieId}`)
            .then(response => {
                this.setState({ movie: response.data });
            })
            .catch(error => {
                console.log('Opps!', error.message);
            })
    }

    // we use a timeout to prevent the api request to fire immediately as we type
    timeout = null;
    
    searchMovie = (event) => {
        this.setState({ title: event.target.value, isSearching: true })

        clearTimeout(this.timeout);

        this.timeout = setTimeout(() => {
            axios.get(`http://www.omdbapi.com/?apikey=YOUR_API_KEY&s=${this.state.title}`)
                .then(response => {

                    if (response.data.Search) {
                        const movies = response.data.Search.slice(0, 5);
                        this.setState({ searchResults: movies });
                    }
                })
                .catch(error => {
                    console.log('Opps!', error.message);
                })
        }, 1000)


    }

    // event handler for a search result item that is clicked
    itemClicked = (item) => {
        this.setState(
            {
                movieId: item.imdbID,
                isSearching: false,
                title: item.Title,
            }
        )
    }


    render() {
        return (
            <div onClick={() => this.setState({ isSearching: false })}>
                <Search
                    defaultTitle={this.state.title}
                    search={this.searchMovie}
                    results={this.state.searchResults}
                    clicked={this.itemClicked}
                    searching={this.state.isSearching} />

                <MovieCard movie={this.state.movie} />
            </div>
        );
    }
}

export default MovieSearch;
```


This container is used to handle the state and update changes in our application.
The code above simply loads a an initial movie when it mounts. Whenever we search and update the movieId state, it updates the content of the MovieCard via props.

## Conclusion
You might think that this was a little bit rushed. Remember, this is not a tutorial but a challenge for beginners that feel they can think in React. My code was just a guide. Thanks for reading and i hope to see you in the next part.

I don't think i would throw this one away ;)

Link to Part 1: [Here](https://dev.to/fleepgeek/react-throwaway-app-1-currency-converter--52c7)