In Front-End Development - 2 months, 3 weeks ago

GatsbyJS: Build a user listing web app. (Part 3)

Our application lacks a user search feature. We will add that functionality using JS Search and Axios.

Introduction

This was what we achieved in part 2 of the tutorial.

  1. Added gatsby-source-apiserver plugin to request data from an external API.
  2. Used GraphiQL to structure our query.
  3. Populated the home page with user data.
  4. Created a details page to displays user's details.
  5. Split our pages into smaller components.

Adding Search

Our application is working as it should but what if there was a hundred, a thousand or even one million user profiles?

Searching for a specific user would be akin to looking for a needle in a haystack.

We can solve this problem by adding search functionalities.

To do that, we first install the js-search and axios plugin.

$ npm install --save js-search axios

 

Add a SearchContainer.js file inside src/components folder.

$ touch src/components/SearchContainer.js

 

Paste the following code in SearchContainer.js.

import React, { Component } from "react"
import Axios from "axios"
import * as JsSearch from "js-search"
import Profile from "./profile"

class Search extends Component {
  state = {
    peopleList: [],
    search: [],
    searchResults: [],
    isLoading: true,
    isError: false,
    searchQuery: "",
  }
  /**
   * React lifecycle method to fetch the data
   */
  async componentDidMount() {
    Axios.get("https://randomuser.me/api/?results=10")
      .then(result => {
        const peopleData = result.data
        this.setState({
          peopleList: peopleData.results,
         })
        this.rebuildIndex()
      })
      .catch(err => {
        this.setState({ isError: true })
        console.log(`${err}`)
      })
  }

  /**
   * rebuilds the overall index based on the options
   */
  rebuildIndex = () => {
    const { peopleList } = this.state
    const dataToSearch = new JsSearch.Search("results")
    /**
     *  defines a indexing strategy for the data
     * more more about it in here https://github.com/bvaughn/js-search#configuring-the-index-strategy
     */
    dataToSearch.indexStrategy = new JsSearch.PrefixIndexStrategy()
    /**
     * defines the sanitizer for the search
     * to prevent some of the words from being excluded
     *
     */
    dataToSearch.sanitizer = new JsSearch.LowerCaseSanitizer()
    /**
     * defines the search index
     * read more in here https://github.com/bvaughn/js-search#configuring-the-search-index
     */
    // dataToSearch.searchIndex = new JsSearch.TfIdfSearchIndex("email")

    dataToSearch.addIndex('email') // sets the index attribute for the data
    dataToSearch.addIndex("phone") // sets the index attribute for the data

    dataToSearch.addDocuments(peopleList) // adds the data to be searched
    this.setState({ search: dataToSearch, isLoading: false })
  }

  /**
   * handles the input change and perfom a search with js-search
   * in which the results will be added to the state
   */
  searchData = e => {
    const { search } = this.state
    const queryResult = search.search(e.target.value)
    this.setState({ searchQuery: e.target.value, searchResults: queryResult })
  }
  handleSubmit = e => {
    e.preventDefault()
  }

  render() {

    const { peopleList, searchResults, searchQuery } = this.state
    const queryResults = searchQuery === "" ? peopleList : searchResults

return (
<div>
  <div>
    <form onSubmit={this.handleSubmit}>
      <div>
        <span>Search</span>
      </div>
      <input type="text" value={searchQuery} onChange={this.searchData} placeholder="Enter your search here" />
    </form>
  </div>
  Number of items:&nbsp;
  {queryResults.length}
  <div>
    <table>
      <thead>
        <tr>
          <th>
            Name
          </th>
          <th>
            Gender
          </th>
          <th>
            Email
          </th>
          <th>
            Number
          </th>
          <th>
            Image
          </th>
        </tr>
      </thead>
      <tbody>
        {queryResults.map(function(item, index) {
        return (
        <Profile props={item} key={index} />
        )
        })}
      </tbody>
    </table>
  </div>
</div>
  )}
}
export default Search

The code may look confusing but it is actually quite straightforward.

  • Initialise empty states for the data and search results.
  • Use Axios to fetch data from the endpoint.
  • Store the data in peopleList state.
  • Configure options for search indexing.
  • Set fields to search. (email and phone)
  • Add matched results to the current state. 

Let's refactor our existing code.

Edit index.js.

...
import { graphql } from "gatsby"
import Search from "../components/SearchContainer"
...
...
export default ({ data }) => {
  return (
    <div>
        <Search />
    </div>
  )
}

 

Replace the code for profile.js with the following.

import React from "react"
import { Link } from "gatsby"

export default ({props}, index) => {

return (
  <tr key={`row_${props.email}`}>
  <th>
    <Link to={`/details/`} state={{props}}>{props.name.title} {props.name.first} {props.name.last}</Link>
  </th>
  <td>{props.gender}</td>
  <td>{props.email}</td>
  <td>{props.phone}</td>
  <td>
    <Link to={`/details/`} state={{props}}><img src={props.picture.thumbnail} alt={`${props.name.title} ${props.name.first} ${props.name.last}`} /></Link>
  </td>
</tr>
  )
}

 

Add a details.js component inside src/components.

$ touch ./src/components/details.js

 

Paste the following code into src/components/details.js.

import React from "react"
import { Link } from "gatsby"

export default ({props}) => {

  return (
<div>
  <div>
    <img src={props.picture.large} alt={`${props.name.title} ${props.name.first} ${props.name.last}`} />
    <div>
      <h5>{props.name.title} {props.name.first} {props.name.last}</h5>
      <ul>
        <li><strong>Gender:</strong> {props.gender}</li>
        <li><strong>Email:</strong> {props.email}</li>
        <li><strong>Address:</strong> {props.location.street}, {props.location.city}, {props.location.state}</li>
      </ul>
    </div>
  </div>

  <div>
    <Link to="/">Back to Home</Link>
  </div>
</div>
  )
}

 

Refactor our src/pages/detail.js page to include the new component.

import React from "react"
import Details from "../components/details"

export default ({ location }) => {
const value = location.state.props
return (
  <div>
    <Details props={value} />
  </div>
  )
}

 

Test the search feature by entering a user's email or phone number in the search input field. 

Pretty cool but our application is lacking in the aesthetics department.

In the final part of this tutorial, we will style the site using CSS Modules.


blog comments powered by Disqus