自作ブログを作っています。
技術スタック
- TypeScript
- React
- Next.js
- Styled Components
- GitHub Pages
今回は検索機能を作ります。
今回作るもの
今回は下記の2つを実装します。
- サイドメニューの検索フォーム
- 検索画面
原則全画面で表示されているサイドメニューから検索ができます。
検索を実行すると、検索画面に遷移します。
検索画面では条件を変えて繰り返し検索ができます。
検索フォーム
検索フォームのコンポーネントを作成します。
input searchを含むformにデザイン適用します。
import { ChangeEventHandler, FormEventHandler } from 'react' import styled from 'styled-components' const Form = styled.form` display: flex; justify-content: space-between; ` const SearchInput = styled.input` margin-right: 10px; padding: 7px; border: 1px solid #444; border-radius: 5px; font-size: 1.2rem; ` type Props = { value: string onChange: ChangeEventHandler<HTMLInputElement> onSubmit: FormEventHandler<HTMLFormElement> } const SearchPostForm = ({ value, onChange, onSubmit }: Props) => { return ( <Form onSubmit={onSubmit}> <SearchInput type="search" value={value} onChange={onChange} /> </Form> ) } export default SearchPostForm
続いて、このコンポーネントに対応したHookを作ります。
検索文字列の保持・検索文字列の変更イベントの他、submit時の検索画面への遷移も含んでいます。
遷移の際に、クエリストリング q
として検索文字列を渡します。
import { useRouter } from 'next/router' import { ChangeEventHandler, FormEventHandler, useState } from 'react' const useSearchPostForm = () => { const router = useRouter() const [searchText, setSearchText] = useState('') const onChangeSearchText: ChangeEventHandler<HTMLInputElement> = (e) => { setSearchText(e.target.value) } const onSubmitSearchText: FormEventHandler<HTMLFormElement> = (e) => { e.preventDefault() router.push({ pathname: '/posts/search', query: { q: searchText, }, }) } return { searchText, setSearchText, onChangeSearchText, onSubmitSearchText, } } export default useSearchPostForm
検索画面
検索を実行する画面を作成します。
具体的には、検索文字列によるタイトルの絞り込みをします。
検索のためのタイトル一覧と、ページ遷移のためのslug一覧を取得しておきます。
export async function getStaticProps() { return { props: { matters: await postService.getMatters(), slugs: await postService.getSlugs(), }, } }
next/routerを用いて検索文字列をクエリストリングより取り出します。
const q: string = (() => { if (typeof router.query.q === 'undefined') { return '' } if (Array.isArray(router.query.q)) { return router.query.q.join('') } return router.query.q })()
先ほど作成したHookの中身を取り出します。
const { searchText, setSearchText, onChangeSearchText, onSubmitSearchText } = useSearchPostForm()
クエリストリングの指定があれば、input search に適用します。
// 初回定義時には、qは空文字列になるため、setTextで更新する必要がある. // 更新による無限ループを避けるため、useEffectで囲う. useEffect(() => { setSearchText(q) }, [setSearchText, q])
検索(絞り込み)を行います。
const searchedList = matters .filter(({ title }) => title.includes(searchText)) .sort((a, b) => (a.date < b.date ? -1 : 1)) .map(({ title, slug }) => ( <li key={slug}> <Link href={'/posts/' + slug}> <a>{title}</a> </Link> </li> ))
最後にこれらを画面表示します。
return ( <MainContainer matters={matters}> <section> <H1>検索</H1> <Content> <StyledSearchPostForm value={searchText} onChange={onChangeSearchText} onSubmit={onSubmitSearchText} /> <ul>{searchedList}</ul> </Content> </section> </MainContainer> )
これにて検索機能が完成しました。
Pull Request
https://github.com/nek0meshi/blog/pull/13
あとがき
今後は本文検索も組み込みたいですね。