Engenharia

Como construir uma busca na aplicação com o React e o Elastic App Search

Quando um usuário faz uma busca, ele quer receber resultados relevantes. Mas resultados relevantes são apenas parte do que conquistará a simpatia do visitante; a busca também deve ser uma boa experiência. Deve ser rápida, reagir às entradas e parecer inteligente e eficaz.

Neste tutorial, demonstraremos como criar uma experiência de busca fluida e robusta na aplicação usando o React e o cliente JavaScript do Elastic App Search. No final, você terá uma aplicação com React de boa aparência e relevante que lhe permitirá fazer buscas em vários pacotes do npm em tempo real com classificação por facetas e o estado mantido como parte do URI.

O código completo pode ser encontrado no GitHub.


Uma observação importante:

Temos um artigo mais recente sobre como criar ótimas buscas ainda mais rapidamente usando o Search UI, uma biblioteca open source da Elastic.

Não deixe de lê-lo!


Requisitos

Para prosseguir, você precisará ter o seguinte:

  1. Uma versão recente do Node.js.
  2. Uma versão recente do npm.
  3. Uma conta do Elastic App Search Service ou uma avaliação gratuita de 14 dias ativa.
  4. Cerca de 30 minutos.

App Search: uma introdução

As aplicações são construídas em torno de dados. O Facebook explodiu em nossos círculos sociais ao apresentar dados de “amigos” de uma forma interessante. O eBay começou como a forma mais simplificada de encontrar e comprar produtos usados. A Wikipédia facilitou o aprendizado dos leitores sobre... bem, tudo!

As aplicações existem para resolver problemas de dados. Nesse esforço, a busca é uma companheira essencial. Se for uma aplicação importante, uma parte fundamental do que ela oferece será a busca: encontrar amigos, produtos, conversas ou artigos. Quanto maior e mais interessante for o conjunto de dados, mais popular será a aplicação — especialmente se a busca for relevante e recompensadora.

O Elastic App Search foi desenvolvido com base no Elasticsearch, um mecanismo de busca RESTful distribuído e open source. Com o Elastic App Search, os desenvolvedores recebem acesso a um conjunto robusto de endpoints de API otimizados para lidar com casos de uso diferenciados de busca em aplicações.

Coloque seus mecanismos para funcionar

Para começar, crie um mecanismo no App Search.

Um mecanismo ingere objetos para indexação. Um objeto são seus dados; é o perfil do amigo, o produto ou a página wiki. Depois que os dados são ingeridos no App Search, são indexados em um esquema flexível e otimizados para busca. A partir daí, podemos utilizar diferentes bibliotecas de clientes para criar uma experiência de busca agradável.

Para este exemplo, chamaremos nosso mecanismo: node-modules.

Depois que o mecanismo for criado, precisaremos de três itens da página Credencials (Credenciais):

  1. O identificador do host, prefixado com host-
  2. Uma chave de API privada, prefixada com private-
  3. Uma chave de busca pública, prefixada com search-

Com isso, podemos clonar o projeto, entrar no diretório, verificar a ramificação inicial e executar uma instalação do npm:

$ git clone https://github.com/swiftype/app-search-demo-react.git
$ cd react-tutorial && git checkout starter && npm install

Ótimo — temos uma aplicação preparada e pronta. Mas para fazer buscas, são necessários dados...

Ingestão ~

Na maioria dos casos, seus objetos residiriam em um banco de dados ou em uma API de backend. Como este é um exemplo, usaremos um arquivo .json estático. O repositório contém dois scripts: init-data.js e index-data.js. O primeiro é um scraper que foi usado para adquirir dados do node-module bem formatados do npm. Os dados existem no arquivo node-modules.json. O último é um indexador que fará a ingestão desses dados no mecanismo do App Search para indexação.

Para executar o script indexador, precisaremos passar nosso identificador de host e a chave de API privada junto com ele.

$ REACT_APP_HOST_IDENTIFIER={Your Host Identifier} \
REACT_APP_API_KEY={Your Private API Key} \
npm run index-data

Os objetos são enviados rapidamente para nosso mecanismo do App Search em lotes de 100, e o índice é construído.

Agora devemos ter um dashboard para nosso mecanismo recém-criado com aproximadamente 9.500 pacotes do npm indexados como documentos. Pode ser útil dar uma olhada nos dados para nos familiarizarmos com seu conteúdo.

app_search_engine_overview.png

Reatividade

Com nosso mecanismo preenchido e pronto, podemos começar a construir nossa aplicação básica.

$ npm start

Iniciar o npm no diretório do projeto abrirá o padrão do React. Ele extrai seu estilo do App.css — vamos customizá-lo melhor para atender às nossas necessidades.

Num futuro próximo, precisaremos de uma caixa de busca onde possamos digitar nossas consultas. Os usuários procurarão esses retângulos úteis porque os mecanismos de busca e os navegadores os habituaram a esse formato: digite aqui e encontre o que deseja!

//App.css
...
.App-search-box {
height: 40px;
width: 500px;
font-size: 1em;
margin-top: 10px;
}

Também precisaremos colocar nossas credenciais de acesso do App Search em um local seguro, como um arquivo .env.

Crie um dentro do diretório raiz do projeto e preencha-o, assim:

//.env
REACT_APP_HOST_IDENTIFIER={seu identificador do host, prefixado com host-}
REACT_APP_SEARCH_KEY={sua chave de busca pública, prefixada com search-}

Com as variáveis guardadas com segurança, podemos começar a escrever nossa lógica de busca.

Começando a buscar

O arquivo App.js é onde a lógica principal ficará. Esse arquivo — junto com a maioria dos outros arquivos iniciais — foi criado por create-react-app, uma ferramenta para ajudar a inicializar aplicações do React sem qualquer configuração. Antes de escrevermos alguma lógica para testar a busca, precisamos instalar a biblioteca do cliente JavaScript do Swiftype App Search:

$ npm install --save swiftype-app-search-javascript

Coloque o código a seguir no App.js. Ele realizará uma busca básica.

Codificaremos foo como nosso termo de busca de exemplo:

import * as SwiftypeAppSearch from "swiftype-app-search-javascript";
const client = SwiftypeAppSearch.createClient({
hostIdentifier: process.env.REACT_APP_HOST_IDENTIFIER,
apiKey: process.env.REACT_APP_SEARCH_KEY,
engineName: "node-modules"
});
//Podemos consultar qualquer coisa -- foo é o nosso exemplo.
const query = "foo";
const options = {};
client.search(query, options)
.then(resultList => console.log(resultList))
.catch(error => console.log(error))

O navegador será atualizado e criará uma matriz resultList via console.log. Para explorar a matriz, podemos abrir o console do desenvolvedor do nosso navegador. Podemos tentar mais algumas consultas substituindo a consulta foo por outra string. Depois que a consulta for alterada e a página atualizada, podemos ver como o conjunto de resultados se adaptou.

Ótimo — com isso, já estamos buscando em nossos node-modules.

Bondade resultante

Temos um padrão simples para buscar, mas os resultados são de pouca utilidade se ficam escondidos em um console.log. Vamos remover o estilo do React básico e nosso código anterior e, em seguida, estender as coisas.

Vamos criar...

  1. Uma variável de estado que conterá uma propriedade de resposta.
  2. Um método performQuery que consultará o App Search usando client.search. Ele armazenará os resultados da consulta na propriedade de resposta.
  3. Um gancho de ciclo de vida componentDidMount, que será executado uma vez após o carregamento da aplicação. Consultaremos foo novamente, mas poderemos usar qualquer consulta que desejarmos.
  4. HTML estruturado para armazenar a saída de dados resultante e o número total de resultados.
//App.js
// ... Truncated!
class App extends Component {
state = {
// Uma nova propriedade de estado, que contém a resposta da consulta mais recente
response: null
};
componentDidMount() {
/*Chamar isso em componentDidMount garante que os resultados sejam exibidos na
tela quando o app for carregado pela primeira vez*/
this.performQuery("foo");
}
// Método para realizar uma consulta e armazenar a resposta
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
// Adicione isto por enquanto para que você possa inspecionar a resposta completa
console.log(response);
this.setState({ response });
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response} = this.state;
if (!response) return null;
return (


Busca do módulo de nó



{/*Mostrar a contagem total de resultados desta consulta*/}

{response.info.meta.page.total_results} Results


{/*Iterar os resultados e mostrar seu nome e descrição*/}
{response.results.map(result => (

Name: {result.getRaw("name")}


Description: {result.getRaw("description")}



))}

);
}
}
// ... Truncated!

No momento em que clicarmos em salvar, os resultados aparecerão em http://localhost:3000 — 27 resultados e alguns módulos que soam bem bacana. Se algo deu errado, podemos verificar o console, pois temos dois console.logs aninhados no código.

Criação de uma caixa chique

Estávamos codificando foo em nossas consultas. O que torna a busca mais valiosa é que ela começa com uma expressão livre. Depois de desenvolver uma ótima experiência de busca, você poderá otimizar para as expressões mais comuns, selecionando os conjuntos de resultados mais relevantes. Tudo começa com uma tela em branco: a caixa de busca.

Para criar uma caixa de busca adequada, adicionaremos uma propriedade ao estado chamada queryString. Para manter a propriedade queryString atualizada com novas strings, criaremos um método updateQuery; utilizaremos um manipulador onChange para atualizar queryString e disparar uma nova busca cada vez que um usuário alterar o texto na caixa.

Nossa classe App completa agora se parece com isto:

//src/App.js
// ... Truncated!
class App extends Component {
state = {
// Uma nova propriedade de estado, que rastreia o valor da caixa de busca
queryString: "",
response: null
};
componentDidMount() {
// Remover a busca codificada por "node"
this.performQuery(this.state.queryString);
}
// Manipula o evento onChange toda vez que o usuário digita na caixa de busca.
updateQuery = e => {
const queryString = e.target.value;
this.setState(
{
queryString // Salva a string de consulta inserida pelo usuário
},
() => {
this.performQuery(queryString); // Disparar uma nova busca
}
);
};
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response, queryString} = this.state;
if (!response) return null;
return (


Busca do módulo de nó



{/*Uma caixa de busca, conectada ao valor da string de consulta e ao onChange
handler*/}

);
}
}
// ... Truncated!

Debounce!

Dentro desta iteração, cada vez que uma alteração for detectada na caixa, uma busca ocorrerá, o que pode se tornar intenso em nossos sistemas. Para remediar isso, aplicaremos uma função _debounce_, cortesia da Lodash.

$ npm install --save lodash

Debounce é um método de limitação de taxa do número de solicitações de entrada com base em um número definido de milissegundos. Um usuário está pensando em como formular sua consulta, cometendo erros de digitação ou digitando muito rápido... e assim, não precisamos consultar cada alteração detectada.

Ao agrupar nosso método performQuery em uma função debounce da Lodash, podemos especificar um limite de taxa de 200 ms — 200 ms devem decorrer sem nenhuma entrada antes do início da próxima consulta de busca:

//App.js
// ... Truncated!
import { debounce } from "lodash"; // Importar debounce
// ... Truncated!
performQuery = debounce(queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
}, 200); // 200 milissegundos.
// ... Truncated!

Além de dar uma folga aos nossos servidores, a limitação de taxa pode ajudar a suavizar as consultas dos usuários. Vai longe! Sentir é importante.

A seguir…

Este é o início de uma experiência de busca de qualidade com React. Daqui para a frente, há muitas coisas excelentes a acrescentar. Poderíamos adicionar estilo ou implementar recursos dinâmicos do App Search, como facetas, curadorias ou ajuste de relevância. Ou podemos explorar o Analytics API Suite para descobrir insights valiosos sobre a atividade de busca do usuário.

Se quisermos nos aprofundar realmente, o README na ramificação master estende o tutorial para criar gerenciamento de estado baseado em URI, além de adicionar estilo elegante, paginação, filtragem e busca facetada. Com algumas personalizações estilísticas, pode se tornar a base para uma experiência de busca de alta qualidade.

Resumo

No passado, construir uma busca na aplicação que fosse relevante e recompensadora era complicado. O Elastic App Search é uma maneira conveniente e gerenciada de escrever buscas valiosas e ajustáveis em aplicações web. A melhor parte? Tanto os engenheiros quanto as partes interessadas menos técnicas podem gerenciar os principais recursos em um dashboard elegante e intuitivo. Você pode acessar diretamente o App Search com uma avaliação gratuita de 14 dias, sem necessidade de cartão de crédito.