Next, we will need an Elasticsearch index to host our backend data. In this post, we will use appbase.io for doing this but you can also run your own Elasticsearch cluster and create an index.

Image: Showing an app (aka index) creation process within appbase.io

If you opt for cloning the above dataset, you would have already created an appbase.io app.

Getting Started with Create React App

Now that we have the dataset and indexing figured out, we are ready to get started with building the app.

We will initialize a boilerplate with the CRA setup.

npm install -g create-react-app # install CRA if you don't have it.

create-react-app booksearch # initialize the boilerplate.

cd booksearch

Let’s test the default CRA app by running npm start .

Image: Output at http://localhost:3000 after setting up CRA.

One of the great benefits of using CRA is that it works without requiring to set up a build configuration.

Add ReactiveSearch

Next, install @appbaseio/reactivesearch via npm.

npm install --save @appbaseio/reactivesearch@2.16.1

Note: ReactiveSearch v3 is now available and this post isn’t compatible with that. Installing v2.16.1 (or any other v2.x) ensures that you can follow this post as is.

All the ReactiveSearch components are wrapped inside a container component — ReactiveBase which glues the Elasticsearch index and the ReactiveSearch components together. Edit src/App.js file.

import React, { Component } from 'react';

import { ReactiveBase } from '@appbaseio/reactivesearch'; class App extends Component {

render() {

return (

<ReactiveBase

app="good-books-ds"

credentials="nY6NNTZZ6:27b76b9f-18ea-456c-bc5e-3a5263ebc63d"

>

Hello from Reactive Search!

</ReactiveBase>

);

}

} export default App;

The app and credentials refer to the index name and any credentials you may have set up to authorize access. ReactiveBase also supports a url prop which takes in the Elasticsearch cluster URL.

Note: You should either use read-only credentials or have an authorization middleware that ReactiveBase connects to for ensuring secure access in a production build.

Let’s start the server with npm start .

Image: Output at http://localhost:3000 after adding reactivesearch

Adding UI Components

Components we will need to begin with:

A books data search box, A books ratings filter, A display of books result.

DataSearch

Image: DataSearch component UI using the adjacent snippet.

<DataSearch

componentId="mainSearch"

dataField={["original_title", "authors"]}

queryFormat="and"

/>

componentId is a mandatory prop which requires specifying a unique string that is used internally by ReactiveSearch lib, as well as by some of the other user facing props.

dataField prop tells DataSearch which fields to query on. It can take either a string (single field) or an Array of strings (multiple fields).

queryFormat prop’s and value tells DataSearch to only return those results where the search query is present across all the dataField values.

We will eventually perform more customizations in the following steps. You can find the full reference of DataSearch component over here.

SingleRange

Image: SingleRange component UI from the snippet below.

<SingleRange

componentId="ratingsFilter"

dataField="average_rating_rounded"

title="Book Ratings"

data={[

{ start: 4, end: 5, label: "★★★★ & up" },

{ start: 3, end: 5, label: "★★★ & up" },

{ start: 2, end: 5, label: "★★ & up" },

{ start: 1, end: 5, label: "★ & up" }

]}

/>

data prop here allows us to define the range [start, end] options with a label value. When a user selects one of the range options, a range query is applied on the dataField with the selected range endpoints.

You can find the full reference of SingleRange component over here.

ResultCard

Image: ResultCard component UI from the snippet below.

<ResultCard

componentId="results"

dataField="original_title"

react={{

and: ["mainSearch", "ratingsFilter"]

}}

onData={(res) => (

{

"image": res.image,

"title": res.title,

"description": res.average_rating + " ★ "

}

)}

/>

As the name suggests, the ResultCard component displays the results in a card layout.

react prop tells the ResultCard component to construct the query based on the individual queries used within the DataSearch and SingleRange components we defined above (referenced by their componentId value).

onData prop is a function that receives one hit object as an argument and returns the card markup.

Full reference of what ResultCard component allows can be found over here.

Combining the Elements

Lets put these three components together in the src/App.js file.

import React, { Component } from 'react';

import {

ReactiveBase,

DataSearch,

SingleRange,

ResultCard

} from '@appbaseio/reactivesearch'; class App extends Component {

render() {

return (

<ReactiveBase

app="good-books-ds"

credentials="nY6NNTZZ6:27b76b9f-18ea-456c-bc5e-3a5263ebc63d"

>

<DataSearch

componentId="mainSearch"

dataField={["original_title", "original_title.search", "authors", "authors.search"]}

queryFormat="and"

iconPosition="left"

/>

<SingleRange

componentId="ratingsFilter"

dataField="average_rating_rounded"

title="Book Ratings"

data={[

{ start: 4, end: 5, label: "★★★★ & up" },

{ start: 3, end: 5, label: "★★★ & up" },

{ start: 2, end: 5, label: "★★ & up" },

{ start: 1, end: 5, label: "★ & up" },

]}

react={{

and: "mainSearch"

}}

/>

<ResultCard

componentId="results"

dataField="original_title"

react={{

"and": ["mainSearch", "ratingsFilter"]

}}

onData={(res)=>({

"image": res.image,

"title": res.original_title,

"description": res.average_rating + " ★ "

})}

/>

</ReactiveBase>

);

}

} export default App;

Let’s start the server to see how the UI looks with npm start .

Image: Our app UI after adding three components :-)

You should see a fully functional UI that works. Type a search query to see the results reflect in the cards UI. The only thing missing at this point is the layout arrangement and the styles. We will add these in the next step.

Note: We didn’t break a sweat so far and have been able to work around Elasticsearch’s Query DSL complexity, didn’t need to write complex rendering logic nor manage state bindings to our UI view.