React-Reduxとfirebaseで作成したアプリに全文検索機能を導入してみた　後編

##はじめに
　Qiita初投稿になりますので稚拙な文章、またコードもお見苦しい点が多いと思いますがご容赦ください。
またご指摘やご指導くださると幸いです。
 この記事は[React-Reduxとfirebaseで作成したアプリに全文検索機能を導入してみた　前編](https://qiita.com/mamezo-san/items/ee857c728564bce6db9f)
の続きになります。

##自己紹介
　私は昨年末からエンジニアを志し、現在勉強しつつ未経験からの転職を行っている20代後半の看護師です。最近はモダンフロント開発を行えるようになるべくReact-Redux及びFirebaseを用いてアプリの作成を行なっています。

##環境及び参考教材
create-react-app redux material-ui
YOUTUBE　[とらゼミ　トラハックのエンジニア学習講座](https://www.youtube.com/user/1492tiger)
Qiita [Firestore だけで Algolia を使わず全文検索](https://qiita.com/oukayuka/items/d3cee72501a55e8be44a)

##本題
###実施項目
1.textinputで受け取った名前をn-grum関数で分割し、firestoreにマップ型で保存する
2.検索用のフォームを作成する
3.検索フォームに入力された値(以下search)を用いてページ遷移する
4.searchをn-grum関数で分割し、firestoreにクエリを投げる

###実践
2.検索用フォームを作成する。そして3.入力された値をもとにページ遷移を行います。
 トラハックさんの[Drawerメニューを作ろう【日本一わかりやすいReact-Redux講座 実践編#10】](https://www.youtube.com/watch?v=bw_oIwcIGA0&list=PLX8Rsrpnn3IWavNOj3n4Vypzwb3q1RXhr&index=20)
でとてもわかりやすく解説されていたのでそちらを参考に作成していきます。
まず文字を入力するコンポーネントを作成していきます。material-uiのTextFiledを使用します。

```TextInput.jsx
import React from 'react';
import TextField from '@material-ui/core/TextField';

const TextInput = (props) => {
  return(
    <TextField 
        fullWidth={props.fullWidth}
        rows={props.rows}
        value={props.value}
        label={props.label}
        margin="dense"
        multiline={props.multiline}
        required={props.required}
        type={props.type}
        onChange={props.onChange}
    />
  )
}

export default TextInput;
```

次に検索フォームが入っているDrawerの作成。material-uiのDrawerを使用する。

```CloseDrawer.jsx
import React,{useState,useCallback,useEffect} from 'react';
import Drawer from '@material-ui/core/Drawer';
import SearchIcon from '@material-ui/icons/Search';
import { TextInput } from '../UIkit/index';
import { push } from 'connected-react-router';
import IconButton from '@material-ui/core/IconButton';
import { useDispatch } from 'react-redux';

const CloseDrawer = (props) => {

  const { container } = props;
  const dispatch = useDispatch();
  const[search,setSearch] = useState("")

  const inputSearch = useCallback((event) => {
    setSearch(event.target.value)
  },[setSearch]);

  const changeSearch = useCallback((event,search) => {
    dispatch(push(`/?search=${search}`))
    props.onClose(event)
  },[search,setSearch]);
  

  return(
    <nav>
      <Drawer
        container={container}
        variant="temporary"
        anchor="right"
        open={props.open}
        onClose={(e) => props.onClose(e)}
        ModalProps={{keepMounted: true}}
      >
        <div>
          <div>
            <TextInput 
              fullWidth={false} rows={1} multiline={false} value={search}
              type={"text"} label={"キーワードで検索"} required={false}
              onChange={inputSearch}
            />
            <IconButton>
              <SearchIcon onClick={(e) => changeSearch(e,search)} />
            </IconButton>
          </div>
        </div>
      </Drawer>
    </nav>
  )
}

export default CloseDrawer;
```

これで検索フォームの完成
![スクリーンショット 2021-05-10 15.42.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1063240/6282705f-5bf1-6c49-dd0d-7a58db863b22.png)

更にTextInputに入力された値ChangeSearch関数で受け取りpushすることでページを遷移している。
そしてその遷移されたページのURLからsearchする文を抜き出してそれをn-grum関数を使用して分割する。

```List.jsx
    const query = selector.router.location.search;
    const search = /^\?search=/.test(query) ? query.split('?search=')[1]: ""

    useEffect(() => {
      if(search){
        dispatch(searchList(search))
      }
    },[query]);
```

```operations.js
export const searchList = (search) => {
    return async(dispatch) => {
      
        const nGrum = (name,n) => {
            let searchGrums = [];
            for(let i=0; i<name.length; i++){
                const results = [name.substr(i,n)]
                results.map(result => {
                searchGrums.push(result)
            })
            }return searchGrums
        };
        let newSearchGrums = nGrum(search,2);

        newSearchGrums = newSearchGrums.filter(searchGrum => {
            return searchGrum.length > 1 
        });

        let query = db.collection('list').limit(30);
        newSearchGrums.forEach(searchGrum => {
            query = (search !== "" && search !== undefined )? query.where(`word.${searchGrum}`, "==", true) : query
            query.get()
                 .then(snapshots => {
                    const List = []
                    snapshots.forEach(snapshot => {
                        const data = snapshot.data();
                        List.push(data)
                    })
                    dispatch(searchListAction(List))
                })
                })
    }
};
```

分割したsearchGrumをlistの中のn-grumに当てはめてその中に該当するものを表示している。
機能はこれで完成です。（結構シンプル）
＊フィルターはn-grum関数で作成した配列の中には最後の一文字が余って検索に弾かれてしまって表示されないことがある。そのためfilterメソッドで取り除いている。

##まとめ
　いや〜難しいっす。未経験の自分にはこの機能一つを導入するのにも４日程度かかってしまいました。ただ無事導入できてスムーズに全文検索が行えるようになって感動です。完成したアプリがこちらになります
[Menu Memo](https://menu-app-72873.web.app)
![スクリーンショット 2021-05-18 17.24.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1063240/45eb9bec-6099-c5e9-3ddd-b31c13e5b568.png)

同棲を開始してから彼女に美味しいご飯を食べさせたいと毎日作ってきましたが、自分はどんなものを作れるのか、どんなもの作ったことがあるのか忘れてしまうのでそれを記録しようと思い作成しました。headerに今回作成した全文検索がありますのでぜひ試してみてください。ログインページにサンプルのアカウントを置いていますのでそちらでお試しください。
以上で終わりになります。長文、駄文失礼いたしました。
（ps.面接が突破できません。未だ看護師です。)