Let’s Create an Autocomplete Food Search App on React

Let’s Create an Autocomplete Food Search App on React

Video Tutorial

Getting Started

Autocomplete Application is what allows you to easily search for items by just typing relevant text to the actual item names in our case we will be working at a Food Search application that easily can allow you to search for relevant food.

I have already created the structure and the important styles of the application using React and SASS we will be adding the Search and Autocomplete logic.

I used create-react-app CLI for creating and managing the application project.

You can grab the full Source Code from Github.

Home Component

You can imagine it as a multi-page application with a Home page that has a search component to look for different kind of foods.

Please Note that All SASS Styles are provided in the Github project repo, so please consider grabing them.

/* pages/home.jsx */
import React from "react";
import Search from "../components/search";

export default class HomePage extends React.Component {
  render() {
    return (
      <div className="page">
        <div className="page-container">
          <div className="container">
            <Search />
          </div>
        </div>
      </div>
    );
  }
}

And make sure to render component on the App.jsx.

/* App.jsx */
import React from "react";
import logo from "./logo.svg";
import "./App.scss";
import HomePage from "./pages/home";

function App() {
  return (
    <div className="App">
      <HomePage />
    </div>
  );
}

export default App;

On create-react-app Project App.jsx is by default rendered to the DOM on index.js.

Let’s add the Search bar to the middle of our home page.

I already created an elegant design for the search bar, so please consider grabbing the style files from Github Repo.

Now, create a components folder inside src/ and create a search/ folder for the search bar.

Create index.jsx as the main entry point of the Search Component.

import React from "react";

import "./style.scss";
import Popup from "./popup";

export default class Search extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
    };
  }

  render() {
    return (
      <div className="search">
        <div className="search-container">
          <div className="title">Type Food Name</div>
          <div className="content">
            <input
              type="text"
              placeholder="Food"
            />
            <Popup isOpen={true} items={[{name: "Lasagna"}, {name: "Noodles"}]} />
          </div>
        </div>
      </div>
    );
  }
}

Here we have added a simple Search Component that renders a popup underneath, The Search component is pretty straight forward since all it does is rendering a text input with a Popup component at the bottom of the search input.

The Popup component will be responsible for showing a dropdown popup (Popover) that renders the Search result, in this case, it will be rendering a food autocomplete search result.

/* components/search/popup.jsx */
import React from "react";

export default class Popup extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { items, isOpen } = this.props;
    //Do not show popup
    if (!isOpen) return null;
    return (
      <div className="popup">
        <div className="container">
          <div className="content">
            {items &&
              items.map((item, idx) => {
                return (
                  <div className="item" key={idx}>
                    {item.name}
                  </div>
                );
              })}
            {!items && <div className="warning">Nothing Found!</div>}
          </div>
          <div className="footer">Type Keyword to search for food</div>
        </div>
      </div>
    );
  }
}

The Popup component takes a list of items to render on the dropdown alongside an isOpen prop for either to render the Popup or not (Show & Hide).

From the Search Component, we are providing a simple test data as a list of items to render in the Popup, You can check it out by saving and opening your development-server URL in the browser.

Fetch & Autocomplete

It depends on your Application fetching is actually reaching an End-Point and fetching Food data, but in this case, we will be using a simple food.json file that mocks the API END-POINT for providing us with food data.

Here is how the food.json Mocked API Response looks like.

{
  "foods": [
    {
      "name": "Lasagna",
      "price": "12$"
    },
    {
      "name": "Noodles",
      "price": "8$"
    },
    {
      "name": "Chicken Tikka",
      "price": "16$"
    },
    {
      "name": "Soup",
      "price": "6$"
    }
  ]
}

You can add more details about the food like it’s type and if it is available on the menu for today or not.

Make sure to initialize the state with the proper values.

constructor(props) {
  super(props);
  this.state = {
    isPopupOpen: false,
    foods: [],
    errors: [],
    isError: false,
    foundFoods: []
  };
}

The best scenario to fetch the food data and allow to search it for autocomplete is first to fetch it when Search Component is firstly mounted on the DOM that way we can avoid fetching data multiple times when the user interacts with the webpage which can trigger a React re-rendering and we don’t want to hit our server End-Point multiple times with the need to.

/*search/index.jsx*/
...
constructor(props) {
  super(props);
  this.state = {
    foods: [],
  };
}
//Async keyword for working with Promises.
async fetchData() {
  //Use ES6 fetch function to get json 
  const foods = await fetch("/resources/food.json").catch(err => {
    this.setError("Cannot Load Food Data from Server!");
  });
  //Set foods in the state
  this.setState({ foods: (await foods.json()).foods });
}

//Fetch data when the component is firstly mounted 
componentDidMount() {
  //Fetch & hold data on the state
  this.fetchData();
}
...

Now, we got the data in the state firstly when the component is mounted to the DOM. Let’s try to search on the array for the searched keyword by the user.

searchFood(keyword) {
  //Get foods array list 
  const { foods } = this.state;
  //Make sure to safely Escape keyword since it is coming from user's input
  keyword = RegExp.escape(keyword.toLowerCase());
  /*We generate a Regular Expression from the input keyword.
    The Regex allows for having at least one character matched from the actual food name.
    This way we can add autocomplete functionality.
  */
  const pattern = `[A-Za-z.\s]*${keyword}[A-Za-z.\s]*`;
  //Generate a Regex instance from string pattern
  const matchRegex = new RegExp(pattern);
  //Filter found foods that matches the current Regex
  const foundFoods = foods.filter(item =>
    matchRegex.test(item.name.toLowerCase())
  );
  //Set found foods on the state.
  this.setState({ foundFoods });
}

The only thing left is to run searchFood method whenever the user types something on the search bar, so we need to listen for the onChanage event on the input.

onInputChange(e) {
  const keyword = e.target.value;
  this.searchFood(keyword);
}

onInput(e) {
  if (e.target.value !== "") this.showPopup();
  else this.hidePopup();
}

showPopup() {
  this.setState({ isPopupOpen: true });
}

hidePopup() {
  this.setState({ isPopupOpen: false });
}

The onInput method will be triggered when the user starts typing on the input so we could display the Popup to show search result, we also need to make sure the value is not empty.

And we change the state of isPopupOpen with two independent methods in order to either show or hide the search popup.

We lastly need to manage errors by setting and clearing them on the state this way you could inform the user of any technical errors or problems concerning the search.

setError(msg) {
  this.setState(prevState => ({
    errors: [...prevState.errors, msg],
    isError: true
  }));
}

clearAllErrors() {
  this.setState({ errors: [], isError: false });
}

Make sure to render errors properly in order to give the errors better experience and knowledge if there are any errors occurred during the search process.

Finally, just pass in the right state values in order to render Popup and catch the input events properly.

...
render() {
  const { isPopupOpen, foundFoods } = this.state;
  return (
    <div className="search">
      <div className="search-container">
        <div className="title">Type Food Name</div>
        <div className="content">
          <input
            type="text"
            placeholder="Food"
            onInput={this.onInput.bind(this)}
            onChange={this.onInputChange.bind(this)}
          />
          <Popup isOpen={isPopupOpen} items={foundFoods} />
        </div>
      </div>
    </div>
  );
}
...

You can try to search for food on the food.json file and see if the autocompletes works perfectly, consider fetching data from an API endpoint for a reliable search experience.