React Nativeで作ったアプリにAlgoliaの検索を組み込んでみる

昨日はExpo CLIを使ってGetting Started的なことをしてみましたが、今日はReact NativeなアプリにAlgoliaの検索を組み込んでみたいと思います。

expo startして、立ち上がった http://localhost:19002/ で Run on iOS simulator からiOSのエミュレーターを立ち上げてから、App.js を React InstantSearch | ⚡ Lightning-fast search for React and React Native を参考にしつつ↓こんな感じにしてみました。

  1 import React from 'react';
  2 import { StyleSheet, Text, View } from 'react-native';
  3 import { InstantSearch } from 'react-instantsearch-native';
  4 
  5 export default class App extends React.Component {
  6   render() {
  7     return (
  8       <View style={styles.container}>
  9         <InstantSearch
 10           appId="latency"
 11           apiKey="6be0576ff61c053d5f9a3225e2a90f76"
 12           indexName="instant_search"
 13         >
 14           <Text>React Native Getting Started and Algolia Search!</Text>
 15        </InstantSearch>
 16       </View>
 17     );
 18   }
 19 }
 20 
 21 const styles = StyleSheet.create({
 22   container: {
 23     flex: 1,
 24     marginTop: 20,
 25     backgroundColor: '#fff',
 26     alignItems: 'center',
 27     justifyContent: 'center',
 28   },
 29 });

そうすると、コマンドラインではブワーっとそれっぽいログが。

[15:43:07] Finished building JavaScript bundle in 41ms.
[15:43:07] Finished building JavaScript bundle in 150ms.
[15:43:08] Finished building JavaScript bundle in 66ms.
[15:43:08] Finished building JavaScript bundle in 41ms.
[15:43:08] Finished building JavaScript bundle in 41ms.
[15:43:08] Finished building JavaScript bundle in 42ms.
[15:43:08] Finished building JavaScript bundle in 39ms.
[15:43:08] Finished building JavaScript bundle in 42ms.
[15:43:08] Finished building JavaScript bundle in 51ms.
[15:43:08] Finished building JavaScript bundle in 50ms.
[15:43:10] Running application "main" with appParams: {"rootTag":11,"initialProps":{"exp":{"manifest":{"splash":{"resizeMode":"contain","image":"./assets/splash.png","backgroundColor":"#ffffff","imageUrl":"http://127.0.0.1:19001/assets/./assets/splash.png"},"developer":{"projectRoot":"/Users/eshinoha/my-app","tool":"expo-cli"},"loadedFromCache":false,"privacy":"public","logUrl":"http://127.0.0.1:19000/logs","orientation":"portrait","sdkVersion":"32.0.0","mainModuleName":"node_modules/expo/AppEntry","env":{},"platforms":["ios","android"],"xde":true,"id":"@anonymous/my-app-a368c375-e241-411c-9dde-b57b39f80903","hostUri":"127.0.0.1:19000","iconUrl":"http://127.0.0.1:19001/assets/./assets/icon.png","assetBundlePatterns":["**/*"],"name":"My app","slug":"my-app","debuggerHost":"127.0.0.1:19001","icon":"./assets/icon.png","isVerified":false,"packagerOpts":{"lanType":"ip","dev":true,"minify":false,"urlRandomness":"ms-zmb","hostType":"lan"},"ios":{"supportsTablet":true},"updates":{"fallbackToCacheTimeout":0},"bundleUrl":"http://127.0.0.1:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false&assetPlugin=%2FUsers%2Feshinoha%2Fmy-app%2Fnode_modules%2Fexpo%2Ftools%2FhashAssetFiles.js","version":"1.0.0"},"initialUri":"exp://127.0.0.1:19000","appOwnership":"expo","shell":0}}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF

画面表示もイイ感じ。

Screen Shot 2019-02-06 at 15.57.43

続いて検索結果を表示していきます。Connectors というのを使うと、infinite scrollを実現できる、とのこと。今回はconnectInfiniteHitsというコネクタを使って実装していきます。こちらのコネクタにはhits, hasMore, refineというプロパティがあってhitsとhasMoreはまんまだけど、refineっていうのは一番最後まで読み切った時に呼ばれるファンクションらしい。

React Nativeの FlatList を使ってinfinite scrollを実現するそうですがReact Nativeがv0.43未満の場合はListViewを使う必要があるんだそうです。

んま、そんなこんなでコピペ前回でApp.jsを↓こんな風にしてみました。

  1 import React from 'react';
  2 import { StyleSheet, FlatList, Image, Text, View } from 'react-native';
  3 import { InstantSearch, connectInfiniteHits } from 'react-instantsearch-native';
  4 
  5 
  6 export default class App extends React.Component {
  7   render() {
  8     return (
  9       <View style={styles.container}>
 10         <InstantSearch
 11           appId="latency"
 12           apiKey="6be0576ff61c053d5f9a3225e2a90f76"
 13           indexName="instant_search"
 14         >
 15           <Hits/>
 16         </InstantSearch>
 17       </View>
 18     );
 19   }
 20 }
 21 
 22 const Hits = connectInfiniteHits(({ hits, hasMore, refine }) => {
 23   const onEndReached = function() {
 24     if (hasMore) {
 25       refine();
 26     }
 27   };
 28   return (
 29     <FlatList
 30       data={hits}
 31       onEndReached={onEndReached}
 32       keyExtractor={(item, index) => item.objectID}
 33       renderItem={({ item }) => {
 34         return (
 35           <View style={{ flexDirection: 'row', alignItems: 'center' }}>
 36             <Image
 37               style={{ height: 100, width: 100 }}
 38               source={{ uri: item.image }}
 39             />
 40             <View style={{ flex: 1 }}>
 41               <Text>
 42                 {item.name}
 43               </Text>
 44               <Text>
 45                 {item.type}
 46               </Text>
 47             </View>
 48           </View>
 49         );
 50       }}
 51     />
 52   );
 53 });
 54 
 55 const styles = StyleSheet.create({
 56   container: {
 57     flex: 1,
 58     marginTop: 20,
 59     backgroundColor: '#fff',
 60     alignItems: 'center',
 61     justifyContent: 'center',
 62   },
 63 });

itemのimageだけで、nameやtypeが出てないのが激しく気になりますね…笑

Screen Shot 2019-02-06 at 16.43.38

そしてログを見ると、件数多過ぎじゃね?って感じなのかな、、汗

[16:43:12] VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {
[16:43:12]   "contentLength": 6000,
[16:43:12]   "dt": 1452,
[16:43:12]   "prevDt": 1584,
[16:43:12] }

でもって↓を取り除いたら、

 59     backgroundColor: '#fff',
 60     alignItems: 'center',
 61     justifyContent: 'center',

↓のようにそれっぽく表示されるようになりました。しかし、JSを更新するだけで自動でホットロードしてくれて、React Nativeお手軽だなぁ。

Screen Shot 2019-02-06 at 16.51.14

続いて、いよいよ検索窓を付けていきます。今回は connectSearchBox というコネクターを使いますよ、と。今回注目すべきパラメーターは2つあって、currentRefinementという現在のクエリと、refineという新しいクエリが入力された時に呼ばれるファンクション、とのことです。

ということで、importに諸々足して、render()の中でViewの定義追加して、その中にSearchBoxを突っ込んで、const SearchBox = connectSearchBox(({ refine, currentRefinement }) => { を追加して〜、みたいな感じで↓のように。

  1 import React from 'react';
  2 import { StyleSheet, FlatList, Image, Text, View, TextInput } from 'react-native';
  3 import { InstantSearch, connectInfiniteHits, connectSearchBox } from 'react-instantsearch-native';
  4 
  5 
  6 export default class App extends React.Component {
  7   render() {
  8     return (
  9       <View style={styles.container}>
 10         <InstantSearch
 11           appId="latency"
 12           apiKey="6be0576ff61c053d5f9a3225e2a90f76"
 13           indexName="instant_search"
 14         >
 15           <View
 16             style={{
 17               flexDirection: 'row',
 18             }}
 19           >
 20             <SearchBox />
 21           </View>
 22           <Hits/>
 23         </InstantSearch>
 24       </View>
 25     );
 26   }
 27 }
 28 
 29 const Hits = connectInfiniteHits(({ hits, hasMore, refine }) => {
 30   const onEndReached = function() {
 31     if (hasMore) {
 32       refine();
 33     }
 34   };
 35   return (
 36     <FlatList
 37       data={hits}
 38       onEndReached={onEndReached} 
 39       keyExtractor={(item, index) => item.objectID}
 40       renderItem={({ item }) => {
 41         return (
 42           <View style={{ flexDirection: 'row', alignItems: 'center' }}>
 43             <Image
 44               style={{ height: 100, width: 100 }}
 45               source={{ uri: item.image }}
 46             />
 47             <View style={{ flex: 1 }}>
 48               <Text>
 49                 {item.name}
 50               </Text>
 51               <Text>
 52                 {item.type}
 53               </Text>
 54             </View>
 55           </View>
 56         );
 57       }}
 58     />
 59   );
 60 });
 61 
 62 const SearchBox = connectSearchBox(({ refine, currentRefinement }) => {
 63 
 64   const styles = {
 65     height: 60,
 66     borderWidth: 1,
 67     padding: 10,
 68     margin: 10,
 69     flex: 1,
 70   };
 71 
 72   return (
 73     <TextInput
 74       style={styles}
 75       onChangeText={text => refine(text)}
 76       value={currentRefinement}
 77       placeholder={'Search a product...'}
 78       clearButtonMode={'always'}
 79       spellCheck={false}
 80       autoCorrect={false}
 81       autoCapitalize={'none'}
 82     />
 83   );
 84 });
 85 
 86 const styles = StyleSheet.create({
 87   container: {
 88     flex: 1,
 89     marginTop: 20,
 90   },
 91 });

検索窓の位置が若干上過ぎる感はありますが、検索できるようになりました!

Screen Shot 2019-02-06 at 17.09.01

今度は検索結果にハイライトが出るようにしていきます。今回は connectHighligh というコネクタを使います。そしてHighlightというコンポーネント。そして、そろそろTerminalでvimでやりくりするのも疲れてきたので、VS Codeに切り替えます。

無事にconnectInfiniteHitsの中でHighlightを仕込んだところが黄色くハイライトされるようになりました。(↓のキャプチャ、インデントが微妙な件。笑)

Screen Shot 2019-02-06 at 17.53.33

続きはまた明日やっていきたいと思います 😀

シェアする

  • このエントリーをはてなブックマークに追加

フォローする