The Post Where I Figure Out Live Search!

By: Jonny Fluckey | 9/17/2019

Post

"Everyone should know how to program a computer, because it teaches you how to think!"

-Steve Jobs

Whew, it has been a busy summer! But, I am ready to resume blog posts and can hopefully be a little bit more regular.

From a code perspective, I have been learning a few different concepts and languages. I have started learning Angular (which has a lot to like!) and started a project built in Angular which is going to house the balances of my kids bank accounts (For background, anytime my kids get money for their birthday, Christmas, or doing extra chores, I tell them to just give me the money, and I will put it in their bank account, and that I pay a generous interest rate if they save it :)). I hope to have more on that in another post.

For today though, I want to share the next phase of my cocktail database project. In my last post, I detailed how I did an API call from a React front end, through the rails back end, and ultimately to the API endpoint. Once I figured out some Ruby Gems I could use to make the API call easier, it was pretty simple to call the API for a random cocktail recipe.

The next task, which was a little more tricky, was to actually make a call to the API with a user input search to find specific cocktails. This was more difficult for a few reasons:

1. I had to set up the React end to accept an input, handle the change in the input, and make the api call via axios

2. I had to make sure the search input was being passed correctly to the axios call so the rails backend knew what to do with the information

3. I wanted to make this a live search, the the cocktails that were presented showed up in real time as I typed



Step One: Set Up My Rails Back-End



This step should have been easy enough to do since I had already set up the backend previously through the API call to get a random cocktail. That setup ended up being really easy though because with that API call, I was not sending over any parameters, it was simply a call to return a random JSON response. To make the call with parameters, I had to do some additional research and some additional troubleshooting to figure it out. I was able to add an additional method using the RestClient Ruby gem, with some additional setup to accept the search parameters:

require 'rest-client'

class Cocktail

...

def search_cocktail(search)
  
  RestClient.get('https://www.thecocktaildb.com/api/json/v1/1/search.php', {params: {s: search}})

end

end

Then, I had to set up the controller to accept the parameter, and send back the result:

class Api::CocktailsController < ApplicationController

...

  def show
   
  search = Cocktail.new
  @cocktail = search.search_cocktail(params[:id])
  render json: @cocktail

  end

end

I will admit, I had a little bit of a hard time getting the RestClient to accept the parameter correct, and then getting the controller set up just right to accept the parameter and send back the correct results. It was through this process that I got my first real experience utilizing Postman for troubleshooting API calls, which was awesome!

For those who aren't aware, Postman is an application that is great for API management, and where you can easily make API calls, and see the results. What I didn't realize before building this feature was that the program also worked with localhost.So, with that discovery, it made debugging and troubleshooting much easier! 

Step Two: Set up the React Front-End

This step ended up being very similar to what I did with the Random Cocktail component. Passing the data through axios to the backend wasn't too bad. I just had to make the call to the right rails route through the search function:

  search = async val => {
    this.setState({ loading: true });
    const res = await axios(
      `/api/cocktails/${val}`
    )
    const cocktails = await res.data.drinks;
    this.setState({ cocktails, loading: false })
    if (this.state.value === '') {
    this.setState({ cocktails: ''})
    }
  };

Where this ended up getting a little tricker was with the search functionality. With Live Search, it has to be set up where it triggers an API call as the characters are getting typed. This was something I was not super familiar with before, but utilizing Semantic-UI's search module set up, it gave me some great insights into how to set up the search box to do some cool setup (such as a loading indicator), and also how to handle changes in the search box and trigger the search function to run.

Here was the final code for the setup of the Search Component:

import React, { Component } from 'react';
import axios from 'axios';
import { Search, Button, Modal, Segment, Image } from 'semantic-ui-react';
import CocktailSearchDetail from './CocktailSearchDetail';

const style = {
  background: {
    position: 'absolute',
    textAlign: 'center', 
    backgroundColor: '#E4FAFF',
    height: '100%',
    width: '100%',
    padding: '50px',
  }
}

class CocktailSearch extends Component {
  state = {
    cocktails: '',
    loading: false,
    value: ''
  };

...

  onChangeHandler = async e => {
    this.search(e.target.value);
    this.setState({ value: e.target.value });
  };

  renderCocktails = () => {
    let noCocktails = <h3>Could not find a cocktail, try again</h3>;
    if (this.state.cocktails) {
      const drinks =
      this.state.cocktails.map( (drink) => {
        return (
        <div style={{display: 'inline-block', padding: '10px 10px 10px 10px'}}>
        <Image src={drink.strDrinkThumb} size='small' />
        <h3>{drink.strDrink}</h3>
        <Modal trigger={<Button>See Details</Button>}>
          <CocktailSearchDetail key={drink.idDrink} {...drink}/>
        </Modal>
        </div>
        )
        }) ;
        return drinks
    } if (this.state.cocktails === null) {
      return noCocktails;

    } else {
      return (<h3>Fun Awaits! Just Enter a Search</h3>)
    }

  }

  render() {
    return (
      <>
      <div style={style.background}>
        <h1>Search for a Cocktail</h1>
        <br></br>
        <Search
          value={this.state.value}
          onSearchChange={e => this.onChangeHandler(e)}
          loading={this.state.loading}
          showNoResults={false}
        />
        <br></br>
        <h3>Search Results:</h3>
        <Segment raised style={{marginLeft: 'auto', marginRight: 'auto', width: '75%'}}>
        {this.renderCocktails()}
        </Segment>
      </div>
      </>
    );
  }
}

export default CocktailSearch;

And to practice my handy dandy skill of prop drilling while creating some cleaner code, I set up a Modal component to pop up when selecting a specific drink:

import React from 'react';
import { Modal, Image, Table } from 'semantic-ui-react';

function CocktailSearchDetail(props) {

  
  return (
  <>
  <Modal.Header>{props.strDrink}</Modal.Header>
    <Modal.Content image scrolling>
      <Image src={props.strDrinkThumb} wrapped size='medium' />
      <Modal.Description>
        <Table color='blue'>
          <Table.Header>
            <Table.Row>
              <Table.HeaderCell>Amount</Table.HeaderCell>
              <Table.HeaderCell>Ingredient</Table.HeaderCell>
            </Table.Row>
          </Table.Header>
          <Table.Body>
            <Table.Row style={props.strIngredient1 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure1}</Table.Cell>
              <Table.Cell>{props.strIngredient1}</Table.Cell>
            </Table.Row>
            <Table.Row style={props.strIngredient2 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure2}</Table.Cell>
              <Table.Cell>{props.strIngredient2}</Table.Cell>
            </Table.Row>
            <Table.Row style={props.strIngredient3 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure3}</Table.Cell>
              <Table.Cell>{props.strIngredient3}</Table.Cell>
            </Table.Row>
            <Table.Row style={props.strIngredient4 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure4}</Table.Cell>
              <Table.Cell>{props.strIngredient4}</Table.Cell>
            </Table.Row>
            <Table.Row style={props.strIngredient5 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure5}</Table.Cell>
              <Table.Cell>{props.strIngredient5}</Table.Cell>
            </Table.Row>
            <Table.Row style={props.strIngredient6 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure6}</Table.Cell>
              <Table.Cell>{props.strIngredient6}</Table.Cell>
            </Table.Row>
            <Table.Row style={props.strIngredient7 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure7}</Table.Cell>
              <Table.Cell>{props.strIngredient7}</Table.Cell>
            </Table.Row>
            <Table.Row style={props.strIngredient8 === '' ? {display: 'none'}:{}}>
              <Table.Cell>{props.strMeasure8}</Table.Cell>
              <Table.Cell>{props.strIngredient8}</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table>
        <p>Instructions:</p>
        <p>{props.strInstructions}</p>
        <br></br>
      </Modal.Description>
    </Modal.Content>
  </>
  )
}

export default CocktailSearchDetail



This got me to quite a nice spot on the search screen. I think I actually impressed myself for once on how I did this :)



I am now one step closer to having a nice, productive application. I have some more work to do. I want to build quite a few features into this application:

1. The ability for user authentication, so a user could save their favorite recipes (With OAuth2, so people can sign in with one of their other services)

2. Enable search to work with other categories, such as by ingredients, etc.

Those will have to come another day. For now, check out the code in my Github Repo. I hope to add these additional features soon so I can launch a live product into production for all of you to try.