当サイトは、アフィリエイト広告を利用しています

Algoliaでタグ絞り込み機能をつける

作成日:2022月04月08日
更新日:2023年11月30日

Gatsbyのホームページに導入したalgoliaにタグ絞り込み機能を
をつけてみたのでその過程をメモ。

画面イメージ

algolia検索絞り込み

前提

  • 記事をContentfulを使用して取得していること
  • algoliaのindexの記事のタグが登録されていること
  • algoliaの検索をカスタムで作成していること

検索ページを作る。

pagesフォルダ配下にalgoliaの検索ページを作る。

algoliaSearch.jsx
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">
{/* 検索ボックス */}
<div
css={theme => [
css`
position: sticky;
top: 6rem;
box-shadow: 0 0.25rem 1.25rem ${theme.color};
`,
]}
>
<SearchBox searchAsYouType={false} />
</div>
{/* 検索結果 */}
<Hits
css={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になってる...

tagSearch.jsx
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-full
hover:bg-blue-500
duration-300
text-xs font-bold
mr-1 md:mr-2 mb-2 px-2 md:px-4 py-1
opacity-90 hover:opacity-100
`,
css`
display: flex;
justify-content: center;
`,
];

検索結果コンポーネントをカスタマイズする

algoliaのHitsコンポーネントをカスタマイズして
検索結果を選択したタグで絞り込めるようにする

tagSearch.jsx
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 (
<>
<Stats
css={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`]}>
<div
css={[
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};
`,
];

まとめ

やったことをまとめると下記になる

  • タグ選択のstateをuseStateでuseContextで作ったContextに持たせ、共有できるようにする
  • タグ選択のstateを更新できるタグ選択コンポーネントを作成する
  • 検索結果コンポーネントでタグ選択のstateを取得し、検索結果をフィルタリングする。

参考

React InstantSearch

新着記事

タグ別一覧
top