Tutorials

React Powered Movie Search

Build a simple autocompleting search component with React and HoundJS.

Overview

In this guide, we will build a simple ReactJS component that lets you search The Entertainment Graph for movies. You can try the demo to see what it will look like. A user can type into the search box to find a movie, and we'll be performing auto-complete powered by Universal Search with The Entertainment Graph.

Prerequisites

We assume you are familiar with React and ES2015. If you are interested in these technologies, we recommend the React Docs and Babel's intro to ES2015.

The JavaScript ecosystem has plenty of options for compiling and building React projects. We assume you have your preferred way of doing this already, so we will jump straight into the code. If you would like to follow along in our GitHub Repo, we use gulp and Babel to bundle our source code.

GitHub Repo

The completed project can be found in our GitHub Repo.

Getting Started

First things first, let's get a bare-bones HTML page up and running, which we can plug our React component into:

Copy
HTML
<!doctype html>
<html lang="en">
  <head>
    <title>houndjs-example-react-search</title>
    <link rel="stylesheet" href="main.css" />
  </head>
  <body>
    <div id="root"></div>
    <script src="bundle.js"></script>
  </body>
</html>

We are creating an empty root div, which we will mount our React component into. We also import our JavaScript from bundle.js. Your compilation/bundling phase should output to this single JavaScript file: bundle.js.

To get started creating our React search component, let's install a few dependencies:

Copy
bash
npm install --save houndjs react-autosuggest

We are pulling in HoundJS, the JavaScript SDK for The Entertainment Graph. We also are going to be using react-autosuggest, an open-source React component for creating auto completing input fields.

We can now create our search component:

Copy
JS
import React from 'react';

class Search extends Component {

}

Building Suggestions

Let's start integrating the Autosuggest component from react-autosuggest. The react-autosuggest docs explain in great detail how to configure and build an autosuggest component.

To begin with, we need to manage:

  • value: The text inside of the search box.
  • suggestions: An array of search results. In our case, an array of MediaHound Objects.

We will store both of these in the component's state:

Copy
JS
import React from 'react';
import Autosuggest from 'react-autosuggest';

class Search extends Component {
  constructor() {
    super();

    this.state = {
      value: '',
      suggestions: []
    };
  }

  onChange = (event, { newValue }) => {
    this.setState({
      value: newValue
    });
  };

  render() {
    const { value, suggestions } = this.state;
    const inputProps = {
      value,
      onChange: this.onChange,
      placeholder: 'Search for a movie'
    };

    return (
      <Autosuggest suggestions={suggestions}
                   inputProps={inputProps} />
    );
  }
}

Suggestion Values

Since our suggestions are going to be MediaHound Objects, we need to indicate what text should appear in the input box, when a suggestion is selected. We do this by implementing getSuggestionValue:

Copy
JS
class Search extends Component {
  // ...

  getSuggestionValue = (suggestion) => {
    return suggestion.name;
  };

  render() {
    // ...

    return (
      <Autosuggest suggestions={suggestions}
                   getSuggestionValue={this.getSuggestionValue}
                   inputProps={inputProps} />
    );
  }
}

Anytime a MediaHound Object suggestion is selected, we use its name as the text to place in the input box.

Rendering Suggestions

react-autosuggest calls the renderSuggestion function to draw each suggestion:

Copy
JS
class Search extends Component {
  // ...

  renderSuggestion = (suggestion) => {
    let year;
    if (suggestion.releaseDate) {
      year = (
        <div className="searchResult-contributions">
          {(new Date(suggestion.releaseDate * 1000)).getUTCFullYear()}
        </div>
      );
    }
    return (
      <a className="searchResult-link">
        <img src={suggestion.primaryImage.object.thumbnail.url}
            alt={suggestion.name}
            className="searchResult-image" />

        <div className="searchResult-text">
          <div className="searchResult-name">
            {suggestion.name}
          </div>
          {year}
        </div>
      </a>
    );
  };

  render() {
    // ...

    return (
      <Autosuggest suggestions={suggestions}
                   getSuggestionValue={this.getSuggestionValue}
                   renderSuggestion={this.renderSuggestion}
                   inputProps={inputProps} />
    );
  }
}

In renderSuggestion, we return a React component which shows the movie's Primary Image (using the thumbnail size). We also display the movie's name and release date.

Selecting a Suggestion

When the user selects a suggestion, the onSuggestionSelected function is invoked. In this example, we'll just log the suggestion to the console:

Copy
JS
class Search extends Component {
  // ...

  onSuggestionSelected = (event, { suggestion }) => {
    console.log('Movie selected:', suggestion);
  };

  render() {
    // ...

    return (
      <Autosuggest suggestions={suggestions}
                   onSuggestionSelected={this.onSuggestionSelected}
                   getSuggestionValue={this.getSuggestionValue}
                   renderSuggestion={this.renderSuggestion}
                   inputProps={inputProps} />
    );
  }
}

Searching for Suggestions

Now we have a search component that can render results and handle selection. The only thing we don't have is fetching the actual suggestions.

react-autosuggest will invoke onSuggestionsUpdateRequested whenever the user changes the text in the input field. Let's stub that out:

Copy
JS
class Search extends Component {
  // ...

  onSuggestionsUpdateRequested = ({ value }) => {
    // TODO: Fetch the results
  };

  render() {
    // ...

    return (
      <Autosuggest suggestions={suggestions}
                   onSuggestionsUpdateRequested={this.onSuggestionsUpdateRequested}
                   onSuggestionSelected={this.onSuggestionSelected}
                   getSuggestionValue={this.getSuggestionValue}
                   renderSuggestion={this.renderSuggestion}
                   inputProps={inputProps} />
    );
  }
}

Inside of onSuggestionsUpdateRequested, we want to take the user's search string, perform the search using HoundJS, and store the array of resulting MediaHound Objects to the suggestions property on the component's state:

Copy
JS
import { search } from 'houndjs';

onSuggestionsUpdateRequested = ({ value }) => {
  const trimmedValue = value.trim();

  if (trimmedValue.length > 0) {
    search.all({
        searchTerm: trimmedValue,
        scopes: ['movie']
      })
      .then(response => {
        const results = response.content.map(pair => pair.object);

        if (this.state.value === value) {
          this.setState({
            suggestions: results
          });
        }
      });
  }
  else {
    this.setState({
      suggestions: []
    })
  }
};

Mounting the Search Component

Now that our component is finished, we can mount it into the DOM:

Copy
JS
import { render } from 'react-dom';

render(<Search />, document.getElementById('root'));

However, all interactions with the MediaHound API require some form of authentication. In this case, we need to perform Application OAuth. We need to do this before any MediaHound API calls are made. Let's make the authentication call before we even render our component:

Copy
JS
import { render } from 'react-dom';
import { sdk } from 'houndjs';

sdk.configure({
    clientId: 'YOUR_CLIENT_ID',
    clientSecret: 'YOUR_CLIENT_SECRET'
  })
  .then(() => {
    render(<Search />, document.getElementById('root'));
  });

Now we will be authenticated and our API calls will succeed.

Making it Pretty

Let's not forget the most important part. We need to throw a nice coat of paint on our component. In our main.css file, we add:

Copy
.react-autosuggest__container {
  background-color: #eee;
  border-bottom-right-radius: 2px;
  border-bottom-left-radius: 2px;
  box-sizing: border-box;
  font-family: 'Verdana';
  font-size: 14px;
  list-style-type: none;
  margin: 0;
  padding: 0;
  width: 300px;
  z-index: 2;
}

.react-autosuggest__input {
  font-size: 20px;
  width: 300px;
}

.react-autosuggest__suggestions-section-suggestions {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

.react-autosuggest__suggestion {
  color: #111;
  cursor: pointer;
  font-size: 14px;
  height: 70px;
  margin-left: -40px;
  overflow: hidden;
  text-align: left;
}

.react-autosuggest__suggestion--focused {
  background-color: #4A90E2;
  color: #fff;
}

.searchResult-text {
  line-height: 21px;
  overflow: hidden;
  text-align: left;
  white-space: nowrap;
}

.searchResult-image {
  float: left;
  margin-right: 8px;
  width: 40px;
  height: 60px;
}

.searchResult-name {
  font-weight: 500;
}

.searchResult-link {
  display: block;
  padding: 5px;
}

For more details on styling the search component, check out the react-autosuggest docs.