DEV Community

Lisa Jung
Lisa Jung

Posted on

Part 8: Build the client side of the app with React

Table of Content | Read Next: Part 9: Set up the Node.js server to send search request to Elasticsearch and receive results

In this blog, we will be creating the client side of our Earthquakes app with React.

Review

The following is what we will build.

Image description

Our client allows the user to search for earthquakes based on quake type, magnitude, location, and date range. It also allows the user to sort the search results by ascending or descending order of magnitude.

ezgif com-gif-maker (5)

When the user hits the search button, the user input is sent to the server via HTTP request.

The server passes the user input into a Elasticsearch request and sends the request to Elasticsearch.

ezgif com-gif-maker (1)

Elasticsearch retrieves relevant documents and sends the documents to the server. The server sends the documents to the client.

ezgif com-gif-maker

Upon receiving the documents, the client displays the results in the form of cards. Each card contains information about one earthquake.

Image description

Resources

Would you rather watch a video to learn this content? Click on the link below!

Want the code covered in this blog? Click on the link below to access it!

Building the client

Throughout the blog series, we have been working with the earthquake_app directory. In this directory, we have added the server side code under the server directory.

In this blog, we will be adding the client side code to the earthquake_app directory.

Step 1: Create a React app

Open a new tab in the terminal.

cd into the earthquake_app directory and execute the following command.

//in terminal within the earthquake_app directory
npx create-react-app client
Enter fullscreen mode Exit fullscreen mode

Screenshot of the terminal:
Image description

This command will create a React app called client within the earthquake_app directory.

Image description

Step 2: Install Axios

Once the react app is created, cd into the client directory.

//in terminal within the earthquake_app directory
cd client
Enter fullscreen mode Exit fullscreen mode

Screenshot of the terminal:
Image description

We will be using a library called axios to send HTTP requests to our server.

Install axios by executing the following command.

//in terminal earthquake_app/client
npm i axios
Enter fullscreen mode Exit fullscreen mode

Screenshot of the terminal:
Image description

From the code editor, expand the client directory to locate the package.json file.

You will see that the library called axios has been installed(line 9).

Image description

Step 3: Set up the proxy to our server.

In the same file, we will add the "proxy" key and point the proxy at localhost:3001 where our Node.js server is running.

Add the following code between the "scripts" object(line 20) and the "eslintConfig" object(line 21).

//in client/package.json
"proxy": "http://localhost:3001",
Enter fullscreen mode Exit fullscreen mode

Your package.json file should look like this(line 21):
Image description

Step 4: Build the client

When you look within the client directory, you will see the src directory. Expand this directory to access the App.js file.

Image description

Replace the default content of App.js with the following snippet:

//in client/src/App.js
import axios from 'axios';
import { useState } from 'react';
import './App.css';

const App = () => {
  const [chosenType, setChosenType] = useState(null);
  const [chosenMag, setChosenMag] = useState(null);
  const [chosenLocation, setChosenLocation] = useState(null);
  const [chosenDateRange, setChosenDateRange] = useState(null);
  const [chosenSortOption, setchosenSortOption] = useState(null);
  const [documents, setDocuments] = useState(null);

  const sendSearchRequest = () => {
    const results = {
      method: 'GET',
      url: 'http://localhost:3001/results',
      params: {
        type: chosenType,
        mag: chosenMag,
        location: chosenLocation,
        dateRange: chosenDateRange,
        sortOption: chosenSortOption,
      },
    };
    axios
      .request(results)
      .then((response) => {
        console.log(response.data);
        setDocuments(response.data);
      })
      .catch((error) => {
        console.error(error);
      });
  };

  return (
    <div className='app'>
      <nav>
        <ul className='nav-bar'>
          <li>Earthquake Watch</li>
        </ul>
      </nav>
      <p className='directions'>
        {' '}
        Search for earthquakes using the following criteria:
      </p>
      <div className='main'>
        <div className='type-selector'>
          <ul>
            <li>
              <select
                name='types'
                id='types'
                value={chosenType}
                onChange={(e) => setChosenType(e.target.value)}
              >
                <option value={null}>Select a Type</option>
                <option value='earthquake'>Earthquake</option>
                <option value='quarry blast'>Quarry Blast</option>
                <option value='ice quake'>Ice Quake</option>
                <option value='explosion'>Explosion</option>
              </select>
            </li>
            <li>
              <select
                name='mag'
                id='mag'
                value={chosenMag}
                onChange={(e) => setChosenMag(e.target.value)}
              >
                <option value={null}>Select magnitude level</option>
                <option value='2.5'>2.5+</option>
                <option value='5.5'>5.5+</option>
                <option value='6.1'>6.1+</option>
                <option value='7'>7+</option>
                <option value='8'>8+</option>
              </select>
            </li>
            <li>
              <form>
                <label>
                  <input
                    className='form'
                    type='text'
                    placeholder='Enter city, state, country'
                    value={chosenLocation}
                    onChange={(e) => setChosenLocation(e.target.value)}
                  />
                </label>
              </form>
            </li>
            <li>
              <select
                name='dateRange'
                id='dateRange'
                value={chosenDateRange}
                onChange={(e) => setChosenDateRange(e.target.value)}
              >
                <option value={null}>Select date range</option>
                <option value='7'>Past 7 Days</option>
                <option value='14'>Past 14 Days</option>
                <option value='21'>Past 21 Days</option>
                <option value='30'>Past 30 Days</option>
              </select>
            </li>
            <li>
              <select
                name='sortOption'
                id='sortOption'
                value={chosenSortOption}
                onChange={(e) => setchosenSortOption(e.target.value)}
              >
                <option value={null}>Sort by</option>
                <option value='desc'>Largest Magnitude First</option>
                <option value='asc'>Smallest Magnitude First</option>
              </select>
            </li>
            <li>
              <button onClick={sendSearchRequest}>Search</button>
            </li>
          </ul>
        </div>
        {documents && (
          <div className='search-results'>
            {documents.length > 0 ? (
              <p> Number of hits: {documents.length}</p>
            ) : (
              <p> No results found. Try broadening your search criteria.</p>
            )}
            {documents.map((document) => (
              <div className='results-card'>
                <div className='results-text'>
                  <p>Type: {document._source.type}</p>
                  <p>Time: {document._source['@timestamp']}</p>
                  <p>Location: {document._source.place}</p>
                  <p>Latitude: {document._source.coordinates.lat}</p>
                  <p>Longitude: {document._source.coordinates.lon}</p>
                  <p>Magnitude: {document._source.mag}</p>
                  <p>Depth: {document._source.depth}</p>
                  <p>Significance: {document._source.sig}</p>
                  <p>Event URL: {document._source.url}</p>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Let's go over the code.

Heads up!

For reference purposes only, I have included screenshots of code that I will be explaining.

If you need to copy and paste the code, please refer to the code snippet above or the GitHub repo for part 8.

Lines 1-3
We import all the dependencies that we will need to carry out desired tasks.

Image description

Line 1
We import axios to send HTTP request to the server.

Line 2
We import useState hook to manage state.

Line 3
We import App.css to apply styling to the page.

To explain the code in a logical manner, I may skip around a little bit so be sure to pay attention to the line numbers.

Set up the client to display a list of search options and send user input to the server

Lines 36-149
These lines of code are responsible for what the users see when they first interact with our app.

Image description

Let's take a look at lines 38-46.
Image description

Line 40 renders the app name displayed on the page(red box).

Image description

Line 45 renders the directions for users(orange box).

Quake Type
Lines 51-62 create the drop down menu for quake types.

Image description
Image description

The user gets to choose from four options.

  1. Earthquake(line 58)
  2. Quarry Blast(line 59)
  3. Ice Quake(line 60)
  4. Explosion(line 61)

Each option has a value associated with it(red box).

Image description

Lines 54-55
When a user chooses an option from the select control, its value(red box above) is set as state for the chosenType variable.

The state variable for chosenType is defined in line 6.

Image description

Magnitude
The next drop down menu allows the user to select the magnitude level.

Image description

Lines 65-77 create this drop down menu.

Image description

The user gets to choose from five options.

  1. 2.5+ (line 72)
  2. 5.5+ (line 73)
  3. 6.1+ (line 74)
  4. 7+ (line 75)
  5. 8+ (line 76)

Lines 68-69
When a user chooses an option from the select control, its value is set as state for the chosenMag variable.

The state variable for chosenMag is defined in line 7.

Image description

Location
The user can also specify the location of earthquakes they are interested in.
Image description

Lines 80-90 create a form where the user can type in the city, state, or country of interest.

Image description

Lines 86-87
When a user types in the location, user input is set as state for the chosenLocation variable.

The state variable for chosenLocation is defined in line 8.

Image description

Date range
The next drop down menu allows the user to select the date range.

Image description

Lines 93-104 create this drop down menu.
Image description

The user gets to choose from four options.

  1. Past 7 Days (line 100)
  2. Past 14 Days (line 101)
  3. Past 21 Days(line 102)
  4. Past 30 Days (line 103)

Lines 96-97
When a user chooses an option from the select control, its value is set as state for the chosenDateRange variable.

The state variable for chosenDateRange is defined in line 9.

Image description

Sort by Magnitude
The next drop down menu allows the user to sort the search results by descending or ascending level of magnitude.

Image description

Lines 107-116 create this drop down menu.

Image description

The user gets to choose from two options.

  1. Largest Magnitude First (line 114)
  2. Smallest Magnitude First (line 115)

Lines 110-111
When a user chooses an option from the select control, its value is set as state for the chosenSortOption variable.

The state variable for chosenSortOption is defined in line 10.

Image description

Line 119
This line of code creates the search button.

Image description

We set it up so that when the button is clicked, it calls the sendSearchRequest function.

Image description

Lines 13-34
The function sendSearchRequest is defined here.

Image description

Lines 14-24

Image description

In line 14, we create a constant called results.

We specify that we want to send a get(line 15) request to our server(localhost:3001) with a url ending in /results(line 16).

Lines 17-23
Within this request, we pass the params(the user input) that were collected.

Lines 25-33
Image description

We use axios(line 25) to send the get request to our server(line 26).

Remember, when the client sends the user input to the server, the server will pass the user input into a Elasticsearch request and send the request to Elasticsearch.

ezgif com-gif-maker (1)

Elasticsearch will retrieve relevant documents and send these documents to the server. Upon receiving these documents, the server will send the results to the client.

ezgif com-gif-maker

When the client receives a response (i.e. search results) from the server, the client will print the response to the console(line 28).

Line 11
In line 11, we defined the state variable documents.

Image description

Line 29
The client will update the state variable documents to include the documents received from the server.

Lines 31-33
If an error were to occur, we print the error in the console.

So far, we have discussed how we can display a list of search options for the user and how to capture the user input and send it to the server.

Next, we are going to focus on how the client should handle the documents that were retrieved from Elasticsearch.

Set up the client to handle documents received from the server

Lines 123-146
These lines of code handle documents received from the server.

Image description

These lines of code are only rendered when documents are received from the server.

Lines 125-129
We set up a ternary operator to specify that when the number of documents is greater than 0, the number of documents is displayed on the screen.

If the number of documents returned are 0, then display the message "No results found. Try broadening your search criteria."

Lines 130-144
The client runs through the documents(line 130) and creates a card(line 131 -143) for each document in the array. Each card displays the following information about each earthquake.

  1. Type(line 133)
  2. Time(line 134)
  3. Location(line 135)
  4. Latitude(line 136)
  5. Longitude(line 137)
  6. Magnitude(line 138)
  7. Depth(line 139)
  8. Significance(line 140)
  9. Event URL(line 141)

The information is accessed by document._source.name_of_the_field.

Step 5: Add styling to the page.

Open the App.css file located within the src directory.

Replace the default content with the following code snippet.

//in client/src/App.css
@import url('https://fonts.googleapis.com/css2?family=Open+Sans+Condensed:wght@300&display=swap');

* {
  font-family: 'Open Sans Condensed', sans-serif;
}

body {
  background-image: url(https://i.imgur.com/lx59qEN.jpg);
  background-position-x: center;
  background-position-y: center;
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-attachment: fixed;
  background-origin: initial;
  background-clip: initial;
  margin: 0;
}

div {
  color: white;
}

.nav {
  width: 100%;
  float: left;
}

.nav-bar {
  margin: 0;
  padding: 8px;
  height: 50px;
  background-color: rgb(28, 28, 28);
}

.nav-bar li {
  display: inline;
  margin-top: 0px;
  font-size: 2.5em;
}

.directions {
  margin-left: 14px;
  font-size: 1.5em;
}

.main {
  display: flex;
  flex-direction: column;
  font-family: inherit;
}
.type-selector {
  margin-left: -30px;
}

.type-selector li {
  float: left;
  display: inline;
  margin-left: 4px;
  margin-top: -19px;
  font-style: inherit;
}

.type-selector li:focus,
.type-selector li:hover {
  outline: none;
  border: 1px solid #bbbbbb;
}

.form {
  width: 200px;
  height: 16px;
}

.form::placeholder {
  color: black;
  opacity: 1;
}

.type-selector button:hover {
  color: #be3400;
  border: 0.1rem #404040 solid; 
}

.type-selector button {
  height: 23px;
}

.search-results {
  display: block;
  width: 100%;
  margin-left: 13px;
  font-family: inherit;
}

.results-card {
  background-color: rgb(25, 129, 67, 0.7);
  flex: 0 1 29rem;
  height: 23rem;
  border-radius: 10px;
  font-weight: bold;
  border: 1px;
  float: left;
  margin: 1px;
  margin-top: 5px;
  margin-right: 10px;
  padding: 10px epx;
  justify-content: space-between;
  max-width: 400px;
}

.results-text {
  margin-left: 2rem;
  margin-right: 2rem;
}
Enter fullscreen mode Exit fullscreen mode

This code snippet will create the styling seen in the UI. Since this is Elasticsearch and Kibana tutorial, we will not go over this in detail.

Step 6: Start the client
Locate the tab of the terminal where we created the client.

Start your client by executing the following command in the terminal.

//in terminal within the client directory
npm start
Enter fullscreen mode Exit fullscreen mode

Screenshot of terminal:
Image description

You will see that our client is shown on the browser.

Image description

When you click on each select option, it will render the corresponding drop down menu. It will also display a form where a user can type in the location of interest.

ezgif com-gif-maker (5)

Summary
In this blog, we have created a UI, set up our client to capture user input and send the input to the server.

The client is also set up to receive search results from the server and display the results in the form of cards.

What's next?

In the next blog, we will set up our server to pass the user input received from the client into a Elasticsearch request and send it to Elasticsearch.

ezgif com-gif-maker (1)

Elasticsearch will then retrieve the relevant documents and send the documents to the server.

We will set up our server to receive the documents and send the documents to the client so the results could be displayed to the user.

ezgif com-gif-maker

Move on to Part 9 to learn how to do just that!

Top comments (0)