当サイトは、アフィリエイト広告を利用しています
Gatsbyのホームページに導入したalgoliaにタグ絞り込み機能を
をつけてみたのでその過程をメモ。
pagesフォルダ配下にalgoliaの検索ページを作る。
import React from 'react';import algoliasearch from 'algoliasearch';import { InstantSearch } from 'react-instantsearch-dom';import { css } from 'twin.macro';import SearchBox from '../components/algolia/searchBox';import Hits from '../components/algolia/hits';const algoliaClient = algoliasearch(process.env.GATSBY_ALGOLIA_APP_ID, process.env.GATSBY_ALGOLIA_SEARCH_KEY);const searchClient = {...algoliaClient,search(requests) {if (requests.every(({ params }) => !params.query)) {return Promise.resolve({results: requests.map(() => ({hits: [],nbHits: 0,nbPages: 0,page: 0,processingTimeMS: 0,})),});}return algoliaClient.search(requests);},};const Search = () => {return (<div css={[dark]}><InstantSearch searchClient={searchClient} indexName="gatsbyTechBlog">{/* 検索ボックス */}<divcss={theme => [css`position: sticky;top: 6rem;box-shadow: 0 0.25rem 1.25rem ${theme.color};`,]}><SearchBox searchAsYouType={false} /></div>{/* 検索結果 */}<Hitscss={css`padding-bottom: 10px;`}/></InstantSearch></div>);};export default Search;const dark = theme => [css`background-color: ${theme.background};color: ${theme.color};box-shadow: 0 0.25rem 1.25rem ${theme.color};`,];
HisコンポーネントはalgoliaのHOCを使って
カスタマイズしたものを使う
タグを選択するコンポーネントを作成する。
このコンポーネントではuseContextからタグのstateを取得し、
タグの選択と解除に応じて、取得したstateを更新するように実装する
※ContextはdarkModeのstateを管理しているのと同じものを使ってるので名前がthemeになってる...
import React from 'react';import tw, { css } from 'twin.macro';import { useStaticQuery } from 'gatsby';import { useTheme } from '../../../layout_emotion/themeContext';export default function App() {// GraphQLでContetfulからタグを全て取得const {tags: { group: allTagList },} = useStaticQuery(graphql`query {tags: allContentfulBlogPost {group(field: tags___title) {fieldValue}}}`,);// 選択タグのstateをuseContextから取得const { selectedTag, selectTag } = useTheme();// タグ選択処理const push = e => {e.persist();e.preventDefault();// タグが既に選択済かチェックするconst check = selectedTag.find(tag => {return tag === e.target.id;});if (!check) {// 未選択の場合は追加selectTag(selected => [...selected, e.target.id]);} else {// 選択済の場合は削除// filterで選択したタグ以外のlistを作り直すselectTag(selected => selected.filter(tag => tag !== e.target.id));}};return (<div css={[container]}><div css={[tagList]}>タグ絞り込み</div><div css={[tagsGrid]}>{allTagList.map(({ fieldValue: val }) => (<button type="button" key={val} id={val} css={[grigItem(selectedTag, val), tags]} onClick={e => push(e)}>{val}</button>))}</div></div>);}// ここからはemotinoでのスタイリングconst container = () => [css`display: flex;flex-direction: column;align-items: center;`,];const tagList = theme => [css`font-weight: bold;width: 50%;padding: 10px 20px 10px 20px;border-top-left-radius: 10px;border-top-right-radius: 10px;text-align: center;background-color: ${theme.tagColor};box-shadow: 0 0.25rem 1.25rem ${theme.color};`,];const tagsGrid = theme => [css`display: grid;grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));grid-auto-rows: 50px;justify-content: center;align-items: center;gap: 5px;margin: auto;width: 50%;padding: 20px;border-bottom-left-radius: 10px;border-bottom-right-radius: 10px;background-color: ${theme.background};color: ${theme.color};box-shadow: 0 0.25rem 1.25rem ${theme.color};`,];// タグ選択時のスタイル変更const grigItem = (selectTag, select) => {if (!selectTag) {return [tw``,];}// 選択したタグが既に選択済かチェックconst count = selectTag.filter(selected => select === selected);// 選択してない場合はスタイル追加if (count.length !== 0) {return [tw`bg-blue-400`,];}return [tw``,];};const tags = theme => [tw`rounded-fullhover:bg-blue-500duration-300text-xs font-boldmr-1 md:mr-2 mb-2 px-2 md:px-4 py-1opacity-90 hover:opacity-100`,css`display: flex;justify-content: center;`,];
algoliaのHitsコンポーネントをカスタマイズして
検索結果を選択したタグで絞り込めるようにする
import React from 'react';import { connectHits, Snippet, Highlight, Stats } from 'react-instantsearch-dom';import { Link } from 'gatsby';import tw, { css } from 'twin.macro';import { useTheme } from '../../../layout_emotion/themeContext';const Hits = ({ hits, ...props }) => {// 選択中タグ取得const { selectedTag } = useTheme();// 選択中のタグがある場合は結果から絞り込むif (selectedTag.length !== 0) {hits = hits.filter(hit => {const { tags } = hit;return selectedTag.every(tag => {const machTitle = tags.find(tagTitle => tagTitle === tag);if (machTitle) {return true;}return false;});});}return (<>{hits.length !== 0 && (<div {...props}><Cards hits={hits} /></div>)}</>);};// connectHits経由でexportするとpropsから// hitsを取得できるconst Cards = ({ hits, ...props }) => {return (<><Statscss={css`margin-top: 10px;`}/>{hits.map(hit => {return (<div key={hit.objectID} css={[tw`mx-auto my-6 w-5/6 rounded-xl`, dark]}><div css={[tw`overflow-hidden shadow-md`]}><divcss={[tw`px-6 py-4 font-bold`,css`border-bottom: 1px solid #e5e7eb;`,]}><Link to={`/${hit.slug}`} activeClassName="active">{/* 検索結果から検索文字をハイライト */}<Highlight hit={hit} attribute="title" tagName="mark" /></Link></div><div>{hit.tags}</div><div css={[tw`p-3 max-h-48 break-words`]}>{/* 検索結果(切り取り結果)から検索文字をハイライト */}<Snippet hit={hit} attribute="content" tagName="mark" /></div></div></div>);})}</>);};export default connectHits(Hits);const dark = theme => [css`background-color: ${theme.background};color: ${theme.color};box-shadow: 0 0.25rem 1.25rem ${theme.color};`,];
やったことをまとめると下記になる