
Elasticsearch es uno de los motores de búsqueda de texto completo más populares que le permite buscar grandes volúmenes de datos rápidamente, mientras que React es posiblemente la mejor biblioteca para crear interfaces de usuario. Durante los últimos meses, he sido coautor de una biblioteca de código abierto, ReactiveSearch , que proporciona componentes React para Elasticsearch y simplifica el proceso de creación de una interfaz de usuario (UI) de búsqueda.
Esta es la aplicación que construiré en esta historia:

Una breve idea de Elasticsearch
Elasticsearch es una base de datos NoSQL que puede buscar grandes cantidades de datos en poco tiempo. Realiza una búsqueda de texto completo en los datos que se almacenan en forma de documentos (como objetos) examinando todas las palabras en cada documento.
Esto es lo que dicen los documentos de Elasticsearch:
Elasticsearch es un motor de análisis y búsqueda de texto completo de código abierto altamente escalable. Le permite almacenar, buscar y analizar grandes volúmenes de datos rápidamente y casi en tiempo real.Incluso si nunca antes ha usado Elasticsearch, debería poder seguir esta historia y crear su propia búsqueda impulsada por Elasticsearch utilizando React y ReactiveSearch. ?
¿Qué es ReactiveSearch?
ReactiveSearch es una biblioteca de componentes React UI para Elasticsearch. Para buscar datos en Elasticsearch, debe escribir consultas . Luego, deberá formatear y representar los datos JSON en su interfaz de usuario. ReactiveSearch simplifica todo el proceso, ya que no necesita preocuparse por escribir estas consultas. Esto hace que sea más fácil concentrarse en crear la interfaz de usuario.
A continuación, se muestra un ejemplo que genera una interfaz de usuario de cuadro de búsqueda con sugerencias específicas de categoría:

Esto probablemente nos hubiera llevado más de 100 líneas sin la biblioteca y sin conocimiento de Elasticsearch Query DSL para construir la consulta.
En esta publicación, usaré diferentes componentes de la biblioteca para construir la interfaz de usuario final.
Deberías probar la aplicación final antes de profundizar. Aquí está el enlace CodeSandbox para el mismo.
Configurando las cosas
Antes de comenzar a crear la interfaz de usuario, necesitaremos el conjunto de datos que contiene los repositorios de GitHub en Elasticsearch. ReactiveSearch funciona con cualquier índice de Elasticsearch y puede usarlo fácilmente con su propio conjunto de datos.
Para mayor brevedad, puede usar mi conjunto de datos o clonarlo usted mismo siguiendo este enlace y haciendo clic en el botón Clonar esta aplicación . Esto le permitirá hacer una copia del conjunto de datos como su propia aplicación.

Después de ingresar el nombre de una aplicación, el proceso de clonación debería comenzar a importar los repositorios de 26K + a su cuenta.
Todos los repos están estructurados en el siguiente formato:
{ "name": "freeCodeCamp", "owner": "freeCodeCamp", "fullname": "freeCodeCamp~freeCodeCamp", "description": "The //freeCodeCamp.org open source codebase and curriculum. Learn to code and help nonprofits.", "avatar": "//avatars0.githubusercontent.com/u/9892522?v=4", "url": "//github.com/freeCodeCamp/freeCodeCamp", "pushed": "2017-12-24T05:44:03Z", "created": "2014-12-24T17:49:19Z", "size": 31474, "stars": 291526, "forks": 13211, "topics": [ "careers", "certification", "community", "curriculum", "d3", "education", "javascript", "learn-to-code", "math", "nodejs", "nonprofits", "programming", "react", "teachers" ], "language": "JavaScript", "watchers": 8462 }
- Usaremos create-react-app para configurar el proyecto. Puede instalar create-react-app ejecutando el siguiente comando en su terminal:
npm install -g create-react-app
- Una vez instalado, puede crear un nuevo proyecto ejecutando:
create-react-app gitxplore
- Una vez configurado el proyecto, puede cambiar al directorio del proyecto e instalar la dependencia ReactiveSearch:
cd gitxplore npm install @appbaseio/reactivesearch
- También puede agregar un CDN impresionante, que usaremos para algunos íconos, insertando las siguientes líneas
/public/index.html
antes de quetermine la etiqueta:
Buceando en el código
Seguiré una estructura de directorio simple para la aplicación. Estos son los archivos importantes:
src ├── App.css // App styles ├── App.js // App container ├── components │ ├── Header.js // Header component │ ├── Results.js // Results component │ ├── SearchFilters.js // Filters component │ └── Topic.js // rendered by Results ├── index.css // styles ├── index.js // ReactDOM render └── theme.js // colors and fonts public └── index.html
Aquí está el enlace al repositorio final si desea hacer referencia a algo en cualquier momento.
1. Agregar estilos
He escrito estilos receptivos para la aplicación que puede copiar en su aplicación. Simplemente encienda su editor de texto favorito y copie los estilos /src/index.css
desde aquí y /src/App.css
desde aquí respectivamente.
Ahora, cree un archivo /src/theme.js
donde agregaremos los colores y fuentes para nuestra aplicación:
const theme = { typography: { fontFamily: 'Raleway, Helvetica, sans-serif', }, colors: { primaryColor: '#008000', titleColor: 'white' }, secondaryColor: 'mediumseagreen', }; export default theme;
2. Agregar el primer componente ReactiveSearch
Todos los componentes de ReactiveSearch están envueltos alrededor de un componente de contenedor ReactiveBase que proporciona datos de Elasticsearch a los componentes secundarios de ReactiveSearch.
Usaremos esto en /src/App.js
:
import React, { Component } from 'react'; import { ReactiveBase } from '@appbaseio/reactivesearch'; import theme from './theme'; import './App.css'; class App extends Component { render() { return ( GitXplore ); } } export default App;
Para el accesorio app
y credentials
puede usar los que he proporcionado aquí tal como están. Si clonó el conjunto de datos en su propia aplicación anteriormente, puede obtenerlos en la página de credenciales de la aplicación. Si ya está familiarizado con Elasticsearch, puede pasar una propuesta que url
haga referencia a su propia URL de clúster de Elasticsearch.

Alternativamente, también puede copiar sus aplicaciones credentials
desde el panel de aplicaciones. Desplácese sobre la tarjeta de su aplicación y haga clic en Copiar credenciales de lectura .

Después de agregar esto, verá un diseño básico como este:

3. Agregar una búsqueda de datos

A continuación, agregaré un componente DataSearch para buscar en los repositorios. Crea un componente de interfaz de usuario de búsqueda y nos permite buscar en uno o más campos fácilmente. La render
función actualizada en /src/App.js
se vería así:
// importing DataSearch here import { ReactiveBase, DataSearch } from '@appbaseio/reactivesearch'; ... // Adding the DataSearch here ...
El DataSearch
componente va dentro del ReactiveBase
componente y recibe todos los datos necesarios de él para que no tengamos que escribir consultas de Elasticsearch nosotros mismos. Los alrededores div
agregan algunas className
propiedades para el estilo. Estos solo agregan un diseño a la aplicación. Puede revisar todos los estilos en los /src/App.css
que creamos anteriormente. Puede que hayas notado que hemos pasado algunos accesorios al DataSearch
componente.
Así es como funcionan:
componentId
: un identificador de cadena único que usaremos más adelante para conectar dos componentes ReactiveSearch diferentes.filterLabel
: un valor de cadena que se mostrará más adelante en el menú de filtros.dataField
: an array of strings containing Elasticsearch fields on which search has to performed on. You can check the dataset and see that these fields also matches the column name. All fields specified here matches the structure of data, for examplename
refers to the name of repo,description
refers to its description, but there is a field with a.raw
added here,name.raw
which is a multi-field of thename
field. Elasticsearch can index the same data in different ways for different purposes, which we can use to get better search results.placeholder
: sets the placeholder value in the input box.autosuggest
: setting afalse
value for the prop causes the results to update immediately in the results.iconPosition
: sets the position of the ? icon.URLParams
: is aboolean
which tells the component to save the search term in the browser’s URL so we can share a URL to a specific search query. For example, check this link to see all results related to “react”.className
: adds aclass
for styling using CSS.innerClass
: adds aclass
to different sections of a component for styling using CSS. Here, I’ve added aclass
to theinput
box for styling. A detailed description can be found in the docs.
With this, our app should get a working search bar:

4. Adding the Results view
Next, we’ll be adding the Results
component at /src/components/Results.js
and importing it in /src/App.js
.
Here’s how you can write the Results
component:
import React from 'react'; import { SelectedFilters, ReactiveList } from '@appbaseio/reactivesearch'; const onResultStats = (results, time) => ( {results} results found in {time}ms ); const onData = (data) => ( {data.owner}/{data.name} ); const Results = () => ( ); export default Results;
I’ve imported two new components from ReactiveSearch, SelectedFilters
and ReactiveList
. SelectedFilters will render the filters for our ReactiveSearch components at one place:

ReactiveList renders the search results. Here’s how its props work:
dataField
: orders the results usingname
field here.onData
: accepts a function which returns a JSX. The function is passed each result individually. Here we’re generating a basic UI which we’ll modify later.onResultStats
: similar toonData
but for the result stats. The function is passed the number ofresults
found andtime
taken.react
: thereact
prop tells theReactiveList
to listen to changes made byCategorySearch
component, we’ve provided thecomponentId
of theCategorySearch
component here calledrepo
. Later we’ll add more components here.pagination
: aboolean
which tells the ReactiveList to split the results into pages, each page containing the number of results specified in thesize
prop.
Now we can import
and use the Results
component in /src/App.js
. Just add it inside the div
with results-container
class.
... import Results from './components/Results'; ... render() { return( ... ... ) }
With this component, a basic version of our search UI should start coming together:

5. Adding a Header component
Lets create a Header
component at /src/components/Header.js
which we’ll use to render more search filters.
Here’s how to create a simple Header
component:
import React, { Component } from 'react'; import SearchFilters from './SearchFilters'; class Header extends Component { constructor(props) { super(props); this.state = { visible: false, }; } toggleVisibility = () => { const visible = !this.state.visible; this.setState({ visible, }); } render() { return ( GitXplore Toggle Filters ); } } export default Header;
I’ve moved the navigation code in ..
from /src/App.js
here. The Header component has a method which toggles visible in the state. We’re using this to add a class which would make it take up the entire screen size on mobile layout. I’ve also added a toggle button which calls the toggleVisibility
method.
It also renders another component called SearchFilters
and passes all the props from the parent App
component. Let’s create this component to see things in action.
Create a new file /src/components/SearchFilters.js
:
import React from 'react'; const SearchFilters = () => ( Search filters go here! ); export default SearchFilters;
Next, I’ll update the App
component to use the Header
component that we created just now.
6. Updating App component and handling topics in state
We’ll add a state
variable in App
component called currentTopics
which would be an array of currently selected topics in the app.
We’ll then use the currentTopics
and pass them to the Header
and Results
components:
import React, { Component } from 'react'; import { ReactiveBase, DataSearch } from '@appbaseio/reactivesearch'; import Header from './components/Header'; import Results from './components/Results'; import theme from './theme'; import './App.css'; class App extends Component { constructor(props) { super(props); this.state = { currentTopics: [], }; } setTopics = (currentTopics) => { this.setState( currentTopics: currentTopics ); } toggleTopic = (topic) => { const { currentTopics } = this.state; const nextState = currentTopics.includes(topic) ? currentTopics.filter(item => item !== topic) : currentTopics.concat(topic); this.setState({ currentTopics: nextState, }); } render() { return ( ); } } export default App;
The setTopics
method will set whichever topics are passed to it, which we’ll pass to the Header
component. The toggleTopic
method will remove a topic from the state
in currentTopics
if it’s already present and add the topic if it is not present.
We’ll pass the toggleTopic
method to the Results
component:

7. Adding more filters
Lets add more filters to the UI in /src/components/SearchFilters.js
. I’ll be using three new components from ReactiveSearch here, MultiDropdownList
, SingleDropdownRange
and RangeSlider
. The components are used in a similar fashion as we used the DataSearch
component earlier.
Here’s the code:
import React from 'react'; import PropTypes from 'prop-types'; import { MultiDropdownList, SingleDropdownRange, RangeSlider, } from '@appbaseio/reactivesearch'; const SearchFilters = ({ currentTopics, setTopics, visible }) => ( ); SearchFilters.propTypes = { currentTopics: PropTypes.arrayOf(PropTypes.string), setTopics: PropTypes.func, visible: PropTypes.bool, }; export default SearchFilters;
The SearchFilters
component we’ve created above takes in three props from the Header
component, currentTopics
, setTopics
and visible
. The visible
prop is just used to add a className
for styling.
The first component we’ve used here is a MultiDropdownList
which renders a dropdown component to select multiple options. The first MultiDropdownList
has a dataField
of language.raw
. It’ll populate itself with all the languages available in the repositories dataset.

We’ve used another MultiDropdownList
to render a list of topics:
Here’s how the props work here:
componentId
: similar to the previous ReactiveSearch components, this is a unique identifier which we’ll later associate in theResults
component that we created to get search results.dataField
: maps the component to thetopics.raw
field in Elasticsearch.placeholder
: sets the placeholder value when nothing is selected.title
: adds a title for the component in the UI.filterLabel
: sets the label of the components in the removable filters (theSelectedFilters
which we used in theResults
component).size
: tells the component to render a maximum of1000
items in the list.queryFormat
: when set to'and'
as we’ve used here, it gives results which matches all the selected tags (exactly like intersection).defaultSelected
: sets the selected items in the component. Here we’re passingcurrentTopics
which we’ve stored in thestate
at/src/App.js
.onValueChange
: is a function that will be called by the component when we make a change in its value. Here we call thesetTopics
function which we received in the props. Therefore, whenever we select or deselect a value in the component it would update thecurrentTopics
in thestate
of mainApp
component.

The next ReactiveSearch component we’ve used here is a SingleDropdownRange
. It uses a new prop called data
.
Here’s how it works:
The data
prop accepts an array of objects with start
and end
values and shows the specified label
in the dropdown. It’s mapped to the pushed
field in the dataset which is a date type in Elasticsearch. One cool way to specify date range in Elasticsearch is using the now
keyword. now
refers to the current time, now-1M
refers to one month before, now-6M
to six month before and now-1y
to a year before now
.

I’ve used another SingleDropdownRange
component for the created
field in the dataset.
Here I’ve specified year ranges in datetime for different years:

The third component I’ve used is a RangeSlider
which renders a slider UI. I’ve used to RangeSlider
components, one for the stars
field and the other for forks
.
Two main props that this component introduces are range
and rangeLabels
:
range
: prop specifies a range for the data with astart
andend
value.rangeLabels
: prop takes the labels to show below the slider.showHistogram
: is aboolean
prop which shows a histogram with the distribution of data. Here I’ve set it tofalse
since it’s not needed.

Now we just need to connect these filters to the Results
component. We just have to update one line in the ReactiveList
rendered by the Results
component to include the componentId
s of these components.
Update the react
prop in the ReactiveList
that we rendered in the Results
component:
const Results = () => ( );
That should make your results update for all the filters ?

8. Updating the results view
Up until now, we’ve been seeing only a basic version of the results. As the final piece of this app, lets add some flair to the results ✌️
We’ll be using another component inside our Results
components to render different topics.

Here’s how you can create your own at /src/components/Topic
. Feel free to add your own taste ?
import React, { Component } from 'react'; import PropTypes from 'prop-types'; class Topic extends Component { handleClick = () => { this.props.toggleTopic(this.props.children); } render() { return ( #{this.props.children} ); } } Topic.propTypes = { children: PropTypes.string, active: PropTypes.bool, toggleTopic: PropTypes.func, }; export default Topic;
This component renders its children
and adds a click handler to toggle the topics which updates the currentTopics
inside the main App
component’s state.
Next, we just need to update our Results
component at /src/components/Results.js
:
import React from 'react'; import { SelectedFilters, ReactiveList } from '@appbaseio/reactivesearch'; import PropTypes from 'prop-types'; import Topic from './Topic'; const onResultStats = (results, time) => ( {results} results found in {time}ms ); const onData = (data, currentTopics, toggleTopic) => ( {data.owner}/ {data.name} {data.description} { data.topics.slice(0, 7) .map(item => ( {item} )) } {data.stars} {data.forks} {data.watchers} ); const Results = ({ toggleTopic, currentTopics }) => ( onData(data, currentTopics, toggleTopic)} onResultStats={onResultStats} react={{ and: ['language', 'topics', 'pushed', 'created', 'stars', 'forks', 'repo'], }} pagination innerClass={{ list: 'result-list-container', pagination: 'result-list-pagination', resultsInfo: 'result-list-info', poweredBy: 'powered-by', }} size={6} sortOptions={[ { label: 'Best Match', dataField: '_score', sortBy: 'desc', }, { label: 'Most Stars', dataField: 'stars', sortBy: 'desc', }, { label: 'Fewest Stars', dataField: 'stars', sortBy: 'asc', }, { label: 'Most Forks', dataField: 'forks', sortBy: 'desc', }, { label: 'Fewest Forks', dataField: 'forks', sortBy: 'asc', }, { label: 'A to Z', dataField: 'owner.raw', sortBy: 'asc', }, { label: 'Z to A', dataField: 'owner.raw', sortBy: 'desc', }, { label: 'Recently Updated', dataField: 'pushed', sortBy: 'desc', }, { label: 'Least Recently Updated', dataField: 'pushed', sortBy: 'asc', }, ]} /> ); Results.propTypes = { toggleTopic: PropTypes.func, currentTopics: PropTypes.arrayOf(PropTypes.string), }; export default Results;
I’ve updated the onData
function to render more detailed results. You’ll also notice a new sortOptions
prop in the ReactiveList
. This prop accepts an array of objects which renders a dropdown menu to select how you wish to sort the results. Each object contains a label
to display as the list item, a dataField
to sort the results on and a sortBy
key which can either be asc
(ascending) or desc
(descending).
That’s it, your very own GitHub repository explorer should be live!

Useful links
- GitXplore app demo, CodeSandbox and source code
- ReactiveSearch GitHub repo
- ReactiveSearch docs
Hope you enjoyed this story. If you have any thoughts or suggestions, please let me know and do share your version of the app in comments!
You may follow me on twitter for latest updates. I've also started posting more recent posts on my personal blog.