Elasticsearch > Reactivesearchでいい感じの検索SPAを作るをやってみた


Reactivesearch v3でいい感じの検索SPAを30分ぐらいで作る - Qiita

を参考に自分の環境でやってみたメモです。

install react app

npx create-react-app steam-search
cd steam-search

@appbaseio/reactivesearchを入れます。

yarn add @appbaseio/reactivesearch

起動

yarn start

起動が確認できたらsrc/App.jsを変更します。

src/App.js
import React, {Component} from 'react';
import {DataSearch, ReactiveBase, ReactiveList, ResultList, SelectedFilters} from '@appbaseio/reactivesearch';
import './App.css';

const { ResultListWrapper } = ReactiveList;

class App extends Component {
    render() {
        return (
            <div className="main-container">
                <ReactiveBase
                    app="steam-search"
                    url="http://localhost:9200"
                    credentials="elastic:changeme"
                >
                    <DataSearch
                        componentId="title"
                        dataField={["ResponseName"]}
                        queryFormat="and"
                    />
                    <SelectedFilters/>
                    <ReactiveList
                        componentId="resultLists"
                        dataField="ResponseName"
                        size={10}
                        pagination={true}
                        react={{
                            "and": ["title"]
                        }}
                    >
                        {({data}) => (
                            <ResultListWrapper>
                                {
                                    data.map(item => (
                                        <ResultList key={item._id}>
                                            <ResultList.Content>
                                                <ResultList.Title
                                                    dangerouslySetInnerHTML={{
                                                        __html: item.ResponseName
                                                    }}
                                                />
                                            </ResultList.Content>
                                        </ResultList>
                                    ))
                                }
                            </ResultListWrapper>
                        )}
                    </ReactiveList>
                </ReactiveBase>
            </div>
        );
    }
}

export default App;
  • appをElasticsearchに作ったIndex名に変更します。 app="steam-search"
  • 認証を設定してなければ、credentials="elastic:changeme" の行ごと削除します。

以下の画面に変わったらOK。 変わらない場合には下段のCORSを参照して下さい

CORS

画面が真っ白の場合にはCORSの設定がうまく行ってない可能性があります。

from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

その場合には、elasticsearchの設定ファイルelasticsearch.yml
に追加します。

elasticsearch.yml
http.cors.enabled: true
http.cors.allow-origin: "*"

最終形

詳しい内容はReactivesearch v3でいい感じの検索SPAを30分ぐらいで作る - Qiita
を見ていただくとして、以下のように変更します。

app.js

import React, {Component} from 'react';
import {DataSearch, ReactiveBase, ReactiveList, ResultList, SelectedFilters} from '@appbaseio/reactivesearch';
import './App.css';
import './SteamSearch.css'

const {ResultListWrapper} = ReactiveList;

class App extends Component {
    render() {
        return (
            <div className="main-container">
                <ReactiveBase
                    app="steam-search" ← ElasticsearchのIndex名にする
                    url="http://localhost:9200"
                    credentials="elastic:changeme" ←  認証がなければ削除する
                    theme={
                        {
                            typography: {
                                fontFamily: 'Arial, Helvetica, sans-serif',
                                fontSize: '16px',
                            },
                            colors: {
                                titleColor: '#c7d5e0',
                                textColor: '#c7d5e0',
                                backgroundColor: '#212121',
                                primaryColor: '#2B475E',
                            }
                        }
                    }
                >
                    <DataSearch
                        componentId="title"
                        dataField={["ResponseName"]}
                        queryFormat="and"
                        placeholder="enter search term"
                        showIcon={false}
                        title="Steam Search"
                        className="data-search"
                        innerClass={{
                            input: 'input',
                            list: 'list',
                        }}
                    />
                    <SelectedFilters/>
                    <ReactiveList
                        componentId="resultLists"
                        dataField="ResponseName"
                        size={25}
                        pagination={true}
                        react={{
                            "and": ["title"]
                        }}
                        sortOptions={[
                            {label: "Best Match", dataField: "_score", sortBy: "desc"},
                            {label: "Lowest Price", dataField: "PriceInitial", sortBy: "asc"},
                            {label: "Highest Price", dataField: "PriceInitial", sortBy: "desc"},
                        ]}
                        className="result-list"
                        innerClass={{
                            resultsInfo: "resultsInfo",
                            resultStats: "resultStats",
                        }}
                    >
                        {({data}) => (
                            <ResultListWrapper>
                                {
                                    data.map(item => (
                                        <ResultList
                                            key={item._id}
                                            href={`https://store.steampowered.com/app/${item.ResponseID}`}
                                            className="listItem"
                                        >
                                            <ResultList.Image className="image" src={item.HeaderImage}/>
                                            <ResultList.Content>
                                                <ResultList.Title
                                                    dangerouslySetInnerHTML={{
                                                        __html: item.ResponseName
                                                    }}
                                                />
                                                <ResultList.Description>
                                                    <p className="releaseDate">{item.ReleaseDate}</p>
                                                    <p className="price">${item.PriceInitial}</p>
                                                </ResultList.Description>
                                            </ResultList.Content>
                                        </ResultList>
                                    ))
                                }
                            </ResultListWrapper>
                        )}
                    </ReactiveList>
                </ReactiveBase>
            </div>
        );
    }
}

export default App;

css

SteamSearch.css

SteamSearch.css
@font-face {
    font-family: "IonIcons";
    src: url("//code.ionicframework.com/ionicons/2.0.1/fonts/ionicons.eot?v=2.0.1");
    src: url("//code.ionicframework.com/ionicons/2.0.1/fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("//code.ionicframework.com/ionicons/2.0.1/fonts/ionicons.ttf?v=2.0.1") format("truetype"), url("//code.ionicframework.com/ionicons/2.0.1/fonts/ionicons.woff?v=2.0.1") format("woff"), url("//code.ionicframework.com/ionicons/2.0.1/fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg");
    font-weight: normal;
    font-style: normal
}

html {
    min-height: 100%;
}

body {
    background-color: #1B3C53;
    background-image: linear-gradient(315deg, #000000 0%, #1B3C53 74%);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.main-container {
    width: 616px;
    margin-top: 20px;
}

.data-search .input {
    color: white;
}

.data-search .list li {
    background-color: #aaa !important;
    color: #000;
    overflow: hidden;
}

.data-search .list li:target {
    background-color: #666 !important;
}

.resultsInfo {
    position: relative;
}

.resultsInfo:before {
    z-index: 1;
    position: absolute;
    right: 15px;
    top: 0;
    content: "\f123";
    font-family: "IonIcons";
    line-height: 43px;
    color: #7F878C;
    pointer-events: none;
}

.resultsInfo select {
    background-color: #18394D;
    color: #67c1f5;
    outline: 1px solid #000;
    background: transparent;
    background-image: none;
    padding-right: 50px;
}

.resultsInfo option {
    background-color: #417A9B;
    color: #fff;
}

.result-list .resultStats {
    color: #3b6e8c;
}

.result-list .listItem {
    background-color: #16202D;
    padding: 0;
    margin: 2px 0;
    border: 0;
    background: rgba(0, 0, 0, 0.4);
}

.result-list .listItem:hover {
    background: rgba(0, 0, 0, 0.8);
}

.result-list .image {
    width: 120px;
    height: 45px;
}

.result-list article, .result-list article div {
    display: flex;
    align-items: center;
}

.result-list article h2 {
    padding: 0;
}

.releaseDate {
    color: #4c6c8c;
    width: 150px;
}

.price {
    width: 50px;
}

input {
    background-color: #1C3345 !important;
    border: 1px solid #000 !important;
}