JavaScript Development Space

Setup Search Functionality to a Gatsby with Algolia, React Hook Form, Zustand, and ShadcnUI

Add to your RSS feed8 September 202420 min read
Setup Search Functionality to a Gatsby with Algolia, React Hook Form, Zustand, and ShadcnUI

Implementing a powerful, interactive search functionality on your Gatsby blog can dramatically improve user experience. In this guide, we will set up search using Algolia, integrate form handling with React Hook Form, hashing with zustand, and style everything with ShadcnUI to give your site a polished look.

Here is what we want to do:

  • Create an empty Gatsby website with some mdx content
  • Setup algolia search and receive API keys from it
  • Add shadcn UI and create beautiful search panel and trigger
  • Add React Hook Form

Let's begin implementing these features step by step.

Prepare you Gatsby website

1. Setup Gatsby

gatsby new
js
1 What would you like to call your site?
2 √ · gatsby-algolia
3 What would you like to name the folder where your site will be created?
4 Gatsby/ gatsby-algolia
5 Will you be using JavaScript or TypeScript?
6 · TypeScript
7 Will you be using a CMS?
8 · No (or I\'ll add it later)
9 Would you like to install a styling system?
10 · Tailwind CSS
11 Would you like to install additional features with other plugins?
12 · Add responsive images
13 · Generate a manifest file
cd gatsby-algolia

2. Init ShadcnUI and TailwindCSS

Add this code to your tsconfig file

js
1 {
2 "compilerOptions": {
3 // ...
4 "baseUrl": ".",
5 "paths": {
6 "@/*": [
7 "./src/*"
8 ]
9 }
10 // ...
11 }
12 }

Don't forget to add a comma at the end. You can also follow this instructions by shadcn

Create gatsby-node.ts file:

js
1 import * as path from 'path';
2
3 export const onCreateWebpackConfig = ({ actions }) => {
4 actions.setWebpackConfig({
5 resolve: {
6 alias: {
7 '@/components': path.resolve(__dirname, 'src/components'),
8 '@/lib/utils': path.resolve(__dirname, 'src/lib/utils'),
9 },
10 },
11 });
12 };

Now run

npx shadcn@latest init

Answer the questions

bash
1 Would you like to use TypeScript (recommended)? no / yes
2 Which style would you like to use? › Default
3 Which color would you like to use as base color? › Slate
4 Where is your global CSS file? › › ./src/styles/globals.css
5 Do you want to use CSS variables for colors? › no / yes
6 Where is your tailwind.config.js located? › tailwind.config.js
7 Configure the import alias for components: › @/components
8 Configure the import alias for utils: › @/lib/utils
9 Are you using React Server Components? › no

That's it, now let's test it out. First add button

npx shadcn@latest add button

Remove everything from index.ts file inside the pages folder. Than add shadcn button to your index page for testing

js
1 import * as React from 'react';
2 import type { HeadFC, PageProps } from 'gatsby';
3 import { Button } from '../components/ui/button';
4
5 const IndexPage: React.FC<PageProps> = () => {
6 return (
7 <main>
8 <Button>Test</Button>
9 </main>
10 );
11 };
12
13 export default IndexPage;
14
15 export const Head: HeadFC = () => <title>Home Page</title>;

Run

npm run develop

You must to see now test shadcn button.

3. Add some content to your site

Create content folder in a root of gatsby and file what-is-algolia.mdx

Here is the content, thanks to ChatGPT.

mdx
1 ---
2 title: What is Algolia?
3 slug: algolia
4 permalink: algolia
5 date: 2024-09-06
6 author: algolia gatsby
7 category: gatsby
8 type: post
9 tags: ['algolia']
10 desc:
11 Algolia is a powerful search-as-a-service platform that allows developers to easily add fast and
12 relevant search functionality to websites and applications.
13 ---
14
15 In the digital age, delivering fast, relevant, and user-friendly search experiences has become
16 crucial for businesses, especially those with large volumes of content, products, or user-generated
17 data. Algolia, a leading search-as-a-service platform, addresses this need by providing a powerful,
18 customizable, and scalable solution that enables developers to implement real-time search
19 functionality with ease. In this article, we'll explore the key features of Algolia, how it works,
20 its benefits, and how it compares to other search engines.
21
22 ## What is Algolia?
23
24 Algolia is a hosted search engine that allows developers to build and deploy fast, accurate search
25 experiences into their websites, apps, or e-commerce platforms. Unlike traditional search engines,
26 Algolia focuses on delivering search results with lightning speed and high relevancy. It's known for
27 its ability to handle typo-tolerance, instant search, and powerful ranking algorithms that can
28 prioritize the most relevant results.
29
30 With Algolia, developers don’t need to spend time building complex search algorithms from scratch.
31 Instead, they can leverage Algolia’s pre-built APIs to integrate advanced search functionality into
32 their applications in just a few steps.
33
34 ### Key Features of Algolia
35
36 1. **Instant Search** Algolia’s flagship feature is instant search, which delivers results as users
37 type. This provides a fast and seamless experience, especially useful in scenarios like
38 e-commerce where customers expect quick access to relevant products or categories.
39 2. **Real-time Indexing** With Algolia, any updates to your content are reflected instantly in
40 search results. Whether it's adding a new product, modifying an article, or deleting old data,
41 Algolia's indexing is performed in real-time, ensuring users always get up-to-date information.
42 3. **Typo-Tolerance** Algolia is designed to handle user input errors gracefully. It accounts for
43 common typos, spelling mistakes, and other variations without sacrificing the relevancy of search
44 results. For instance, if a user types "iphon" instead of "iPhone," Algolia still provides
45 relevant results.
46 4. **Custom Ranking** and Relevance Algolia allows developers to customize the ranking of search
47 results based on attributes such as popularity, user behavior, or custom business logic. This is
48 especially beneficial for e-commerce platforms that want to prioritize in-stock products or
49 high-margin items.
50 5. **Faceted Search** and Filtering Faceted search enables users to filter results by categories,
51 tags, or other attributes, improving their ability to find exactly what they’re looking for. This
52 feature is critical in content-rich websites, blogs, or e-commerce stores with hundreds of
53 products.
54 6. **Geo-Search** For location-based applications, Algolia offers geo-search capabilities that allow
55 users to search for results near their physical location. This feature is commonly used in local
56 directories, restaurant finders, and real estate platforms.
57 7. **Search Analytics** Algolia provides detailed analytics on user search behavior, showing metrics
58 like top queries, no-result queries, and conversion rates. These insights can help businesses
59 optimize search relevancy and boost engagement.
60 8. **Multi-language Support** Algolia supports multiple languages, making it a good choice for
61 global companies. The platform offers language-specific tools and optimizations to ensure that
62 search experiences are tailored to users’ native tongues.
63
64 ### How Does Algolia Work?
65
66 At the heart of Algolia’s service is its index, a data structure optimized for fast searches. Here’s
67 a breakdown of how Algolia works:
68
69 1. **Indexing Your Data** Before users can search, the data needs to be indexed. Algolia allows
70 developers to upload their data in various formats (like JSON) and store it in the Algolia cloud.
71 Each item in the dataset is indexed as an object with multiple attributes. This might include
72 titles, descriptions, prices, tags, categories, or any other relevant data points.
73 2. **Search Query Execution** When a user enters a search query, Algolia matches the input against
74 the indexed data using its highly optimized search algorithms. These algorithms consider factors
75 like typo tolerance, partial matches, and synonyms to deliver the most relevant results.
76 3. **Customization with APIs** Algolia provides easy-to-use APIs for customizing search behavior.
77 Developers can fine-tune the ranking formula, implement filters, define sorting orders, and even
78 control which attributes should influence the search relevancy. The APIs support popular
79 programming languages such as JavaScript, Python, PHP, and Ruby.
80
81 4. **Ranking and Sorting** Algolia uses a combination of textual relevance and custom business logic
82 to rank and sort search results. For example, an e-commerce site might rank products based on
83 availability, while a blog might prioritize the most recent articles.
84
85 5. **Search Results Display** Once the search query is processed, Algolia returns the most relevant
86 results almost instantaneously. These results can be displayed on the frontend using various UI
87 libraries or custom designs, ensuring they blend seamlessly with your application’s look and
88 feel.
89
90 ## Use Cases for Algolia
91
92 Algolia's flexibility makes it suitable for various industries and applications. Some of the most
93 common use cases include:
94
95 - **E-commerce Search**: Algolia powers product searches for some of the largest online retailers.
96 It ensures customers can find products quickly, even with incomplete or misspelled queries.
97 Features like filtering, sorting, and ranking are also easily customizable.
98 - **Media and Content Websites**: Content-heavy websites, such as news portals or blogs, use Algolia
99 to improve their search functionality. Visitors can quickly find articles, videos, or documents,
100 sorted by relevancy or other criteria.
101 - **SaaS Applications**: Many SaaS platforms integrate Algolia to offer users fast, searchable data,
102 whether it's customer records, project tasks, or shared files.
103 - **Mobile Applications**: With SDKs for mobile platforms, Algolia is also a good choice for mobile
104 apps that require in-app search functionality.
105
106 ### Algolia vs. Other Search Engines
107
108 Algolia is often compared to other search platforms like Elasticsearch and Solr. Here’s how Algolia
109 stands out:
110
111 - **Speed**: Algolia is designed for speed. It provides results in milliseconds, even for complex
112 searches across large datasets.
113 - **Ease of Use**: Algolia's API and developer tools are designed to be straightforward and
114 user-friendly. It doesn’t require deep technical expertise to implement, which reduces time to
115 market.
116 - **Cloud-Based**: Algolia is fully hosted, which means you don’t need to worry about infrastructure
117 management or scaling as your data grows.
118 - **Relevancy**: Algolia is more focused on delivering relevant results compared to competitors,
119 offering typo tolerance, customizable ranking algorithms, and real-time indexing.
120
121 However, some of the drawbacks include:
122
123 - **Cost**: Algolia can be more expensive than self-hosted solutions like Elasticsearch, especially
124 as your dataset grows.
125 - **Customization**: While Algolia provides a lot of customization, some highly specific or advanced
126 use cases may still be better suited for an open-source solution where developers can have
127 complete control over the search infrastructure.
128
129 ### Benefits of Using Algolia
130
131 - **Fast Implementation**: With pre-built APIs and extensive documentation, integrating Algolia into
132 an application takes minutes rather than hours or days.
133 - **Improved User Experience**: By providing fast, relevant search results, Algolia enhances the
134 overall user experience, leading to higher engagement and retention rates.
135 - **Scalability**: Algolia’s infrastructure can handle massive datasets and heavy search traffic,
136 making it suitable for growing businesses.
137 - **Customization**: Developers have full control over how searches are performed and ranked,
138 allowing them to tailor the search experience to their specific business needs.
139 - **Global Availability**: With multiple data centers across the world, Algolia offers low-latency
140 search results regardless of user location.
141
142 ## Getting Started with Algolia
143
144 To start using Algolia, follow these steps:
145
146 1. **Sign Up**: Create an account on the Algolia website.
147 2. **Create an Index**: Upload your data to Algolia and index it.
148 3. **Integrate the API**: Use Algolia’s API to implement search functionality in your app or
149 website.
150 4. **Customize**: Fine-tune the search behavior with Algolia’s customization options like ranking,
151 sorting, and filtering.
152
153 ## Conclusion
154
155 Algolia is a powerful, easy-to-use search-as-a-service platform that enables developers to deliver
156 fast, accurate, and relevant search experiences. Its wide range of features, such as real-time
157 indexing, typo-tolerance, faceted search, and geo-search, make it an ideal solution for e-commerce,
158 content-heavy websites, and mobile apps. While it may come at a cost, the time and resources saved
159 through its rapid deployment and scalability make it a worthwhile investment for businesses looking
160 to improve their search functionality.

4. Provide the content path to Gatsby

Install a gatsby-source-filesystem plugin, and provide the path to it

npm install gatsby-source-filesystem

then

js
1 // gatsby-config.ts
2
3 {
4 resolve: 'gatsby-source-filesystem',
5 options: {
6 name: 'pages',
7 path: './src/pages/',
8 },
9 __key: 'pages',
10 },
11 {
12 resolve: `gatsby-source-filesystem`,
13 options: {
14 name: 'posts',
15 path: `./content`,
16 },
17 },

5. Create a simple post template

Create a file post-template.tsx in templates directory:

tsx
1 import * as React from 'react';
2 import { graphql, HeadFC, PageProps } from 'gatsby';
3 import { MDXProvider } from '@mdx-js/react';
4
5 const PostTemplate: React.FC<PageProps<Queries.GetSinglePostQuery>> = ({
6 data: {
7 mdx: {
8 frontmatter: { title },
9 },
10 },
11 children,
12 }) => {
13 return (
14 <div className='my-16 px-64'>
15 <article className='text-lg'>
16 <h1 className='tracking-wide text-4xl font-medium space-y-5 my-5'>{title}</h1>
17 <MDXProvider> {children}</MDXProvider>
18 </article>
19 </div>
20 );
21 };
22 export const query = graphql`
23 query GetSinglePost($slug: String) {
24 mdx(frontmatter: { slug: { eq: $slug } }) {
25 frontmatter {
26 title
27 }
28 }
29 }
30 `;
31
32 export const Head: HeadFC<Queries.GetSinglePost, unknown> = ({
33 data: {
34 mdx: {
35 frontmatter: { title },
36 },
37 },
38 }) => {
39 return (
40 <>
41 <title>{title}</title>
42 </>
43 );
44 };
45
46 export default PostTemplate;

6. Setup gatsby-plugin-mdx plugin

npm install gatsby-plugin-mdx @mdx-js/react

Add this config to the plugins list, inside a gatsby-config file

js
1 {
2 resolve: 'gatsby-plugin-mdx',
3 options: {
4 extensions: ['.md', '.mdx'],
5 },
6 },

7. Querying for Content with GraphQL

In Gatsby, querying for content is done using GraphQL, a powerful query language integrated into the framework. It allows you to fetch data from your sources like Markdown files, CMS systems, or other APIs. We need just a slug and contentFilePath to create our pages.

ts
1 // gatsby-node.ts
2
3 export const createPages: GatsbyNode[`createPages`] = async ({
4 graphql,
5 actions,
6 reporter,
7 }: CreatePagesArgs) => {
8 const { createPage } = actions;
9 const postTemplate = path.resolve(`src/templates/post-template.tsx`);
10
11 const result = await graphql<Queries.GatsbyNodeCreatePagesQuery>(
12 `
13 query GatsbyNodeCreatePages {
14 allMdx {
15 nodes {
16 frontmatter {
17 slug
18 }
19 internal {
20 contentFilePath
21 }
22 }
23 }
24 }
25 `,
26 );
27
28 if (result.errors) {
29 reporter.panicOnBuild('Error loading MDX result', result.errors);
30 }
31
32 const posts = result.data.allMdx.nodes;
33
34 posts.forEach((node) => {
35 createPage({
36 path: `/${node.frontmatter.slug}`,
37 component: `${postTemplate}?__contentFilePath=${node.internal.contentFilePath}`,
38 context: {
39 slug: node.frontmatter.slug,
40 category: node.frontmatter.category,
41 },
42 });
43 });
44 };

Let's test it:

npm run develop

Now open http://localhost:8000/algolia to see the post.

Install Dependencies

We need to install the zustand, react-hook-form, algoliasearch, gatsby-plugin-algolia, and some Shadcn components:

npm i zustand react-hook-form algoliasearch gatsby-plugin-algolia react-instantsearch

Create an Algolia account

Sign-up on Algolia website.

Algolia Dashboard

Copy the Application ID, Search-Only API Key, and Admin API Key from your Algolia dashboard. Then, create a .env file at the root of your project and add these keys for secure access:

js
1 ALGOLIA_APP_ID = your_application_id;
2 ALGOLIA_SEARCH_KEY = your_search_only_key;
3 ALGOLIA_ADMIN_KEY = your_admin_key;

This setup will allow your Gatsby project to securely access Algolia's services without exposing sensitive credentials in the codebase.

Create Algolia queries file

Inside a lib directory, add algolia-queries.ts file:

ts
1 import escapeStringRegexp from 'escape-string-regexp';
2
3 const pagesIndexName = `Pages`;
4
5 export const searchIndices = [{ name: pagesIndexName, title: pagesIndexName }];
6
7 const pageQuery = `
8 {
9 pages: allMdx(filter: {frontmatter: {type: {in: "post"}}}) {
10 edges {
11 node {
12 id
13 frontmatter {
14 title
15 permalink
16 }
17 excerpt(pruneLength: 5000)
18 }
19 }
20 totalCount
21 }
22 }
23 `;
24
25 function pageToAlgoliaRecord({ node: { id, frontmatter, ...rest } }) {
26 return {
27 objectID: id,
28 ...frontmatter,
29 ...rest,
30 };
31 }
32
33 export const algoliaQueries = [
34 {
35 query: pageQuery,
36 transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord),
37 indexName: pagesIndexName,
38 settings: { attributesToSnippet: [`excerpt:20`] },
39 },
40 ];

then add gatsby-plugin-algolia to config file:

js
1 {
2 resolve: `gatsby-plugin-algolia`,
3 options: {
4 appId: 'YOUR_APP_ID',
5 apiKey: 'YOUR_API_KEY',
6 queries: algoliaQueries,
7 enablePartialUpdates: true,
8 matchFields: ['slug', 'date'],
9 },
10 },

Run

npm run build

This will import the queries we build to algolia search

Index to Algolia

Now check Algolia dashboard

Algolia Data Import Complete

Create a search store

We will hash all search queries to show the last searches.

Create a folder store in a root directory, and searchStore.ts file

ts
1 import { create } from 'zustand';
2
3 interface SearchTerms {
4 searchTerms: Map<number, string>;
5 addSearchTerm: (term: string) => void;
6 }
7
8 const useSearchStore = create<SearchTerms>((set, get) => ({
9 searchTerms: new Map(),
10 addSearchTerm: (searchTerm: string) => {
11 set((state) => {
12 const isSame = Array.from(state.searchTerms.entries()).find(
13 ([number, term]) => searchTerm.includes(term) || searchTerm.startsWith(term),
14 );
15 const isMoveKeys = state.searchTerms.size > 1 && !isSame;
16
17 const clonedMap = new Map<number, string>();
18 if (isMoveKeys) {
19 state.searchTerms.forEach((val, key) => {
20 if (key !== 3) {
21 clonedMap.set(key + 1, val);
22 }
23 });
24 }
25
26 return {
27 searchTerms: isMoveKeys
28 ? clonedMap.set(1, searchTerm)
29 : isSame
30 ? new Map(state.searchTerms).set(state.searchTerms.size, searchTerm)
31 : clonedMap.set(1, searchTerm),
32 };
33 });
34 },
35 }));
36
37 export default useSearchStore;

Now, each time a user enters a search query, it will be hashed, and the most recent searches will appear in our form for easy reference.

Create Search Component

Add dialog component from Shadcn

npx shadcn@latest add dialog

Add input component

npx shadcn@latest add input

Install ScrollArea and VisuallyHidden from @radix-ui

npm install @radix-ui/react-visually-hidden @radix-ui/react-scroll-area

Add a form component (ShadcnUI)

npx shadcn@latest add form

Create a search form (trigger)

Make static search queries, for default search block:

Create a file static-search.queries.ts inside the lib folder:

ts
1 // static-search.queries.ts
2
3 type StaticSearchQuery = {
4 name: string;
5 href: string;
6 };
7
8 export const StaticSearchQueries: StaticSearchQuery[] = [
9 {
10 name: 'Article #1',
11 href: '/1',
12 },
13 {
14 name: 'Article #2',
15 href: '/2',
16 },
17 {
18 name: 'Article #3',
19 href: '/3',
20 },
21 ];

Now create a folder search inside a components folder. Create also folders blocks and forms inside a search.

Let's begin from block...create file header-search.block.tsx

ts
1 import * as React from 'react';
2
3 import { Input } from '../../ui/input';
4 import { SearchIcon } from 'lucide-react';
5
6 type HeaderSearchBlockProps = {
7 setIsOpen: (bool: boolean) => void;
8 };
9
10 const HeaderSearchBlock = ({ setIsOpen }: HeaderSearchBlockProps) => {
11 return (
12 <div
13 className='relative'
14 onClick={() => {
15 console.log('IsOpen', setIsOpen);
16 setIsOpen(true);
17 }}
18 >
19 <SearchIcon className='absolute left-2.5 top-2 h-4 w-4 text-muted-foreground' />
20 <Input
21 type='search'
22 placeholder='Search...'
23 className='hidden md:inline-block pl-8 h-8 focus-visible:ring-offset-1 focus-visible:ring-1'
24 />
25 </div>
26 );
27 };
28 export default HeaderSearchBlock;

Now create a search-form.tsx, and search-form.props.tsx inside the forms directory

tsx
1 // **search-form.props.tsx**
2
3 import * as React, { DetailedHTMLProps } from 'react';
4
5 export interface SearchFormProps
6 extends DetailedHTMLProps<HTMLDivElement<HTMLDivElement, unknown>, HTMLDivElement> {
7 query: string;
8 refine: (string) => void;
9 }

Create also private _components folder inside the forms, with 2 components.

  1. Default Search - default screen of your search
tsx
1 // default-search-form.tsx
2
3 import * as React from 'react';
4 import { Link } from 'gatsby';
5 import { Activity } from 'lucide-react';
6
7 import useSearchStore from '../../../../store/searchStore';
8 import { StaticSearchQueries } from '../../../../lib/queries/search/static-search.queries';
9
10 type DefaultSearchFormProps = {
11 refine: (string) => void;
12 };
13
14 const DefaultSearchForm = ({ refine }): DefaultSearchFormProps => {
15 const searchTerms = useSearchStore((state) => state.searchTerms);
16
17 React.useEffect(() => {
18 if (!searchTerms) {
19 return;
20 }
21 }, [searchTerms]);
22
23 return (
24 <div className='space-y-4 px-2 py-4'>
25 <div>
26 <div className='mb-2 px-2 text-xs font-semibold uppercase text-slate-600 dark:text-slate-300'>
27 Recent
28 </div>
29 <ul>
30 {searchTerms &&
31 Array.from(searchTerms.entries())
32 .sort((a, b) => a[0] - b[0])
33 .map(([number, term]) => {
34 return (
35 <li
36 key={term}
37 onClick={(e) => refine(term)}
38 className='group flex items-center rounded px-2 py-1 text-sm leading-6 outline-none focus-within:bg-slate-50 hover:bg-slate-50 dark:focus-within:bg-slate-800 dark:hover:bg-slate-800 text-black dark:text-white cursor-pointer'
39 >
40 <Activity
41 size={12}
42 className='fill-slate-400 dark:fill-slate-600 mr-3 group-hover:animate-pulse group-hover:scale-125'
43 />
44 <span>{term}</span>
45 </li>
46 );
47 })}
48 </ul>
49 </div>
50 <div>
51 <div className='mb-2 px-2 text-xs font-semibold uppercase text-slate-600 dark:text-slate-300'>
52 Suggestions
53 </div>
54 <ul>
55 {StaticSearchQueries.map((searchQuery) => {
56 return (
57 <li key={searchQuery.name}>
58 <Link
59 className='group flex items-center rounded px-2 py-1 text-sm leading-6 outline-none focus-within:bg-slate-50 hover:bg-slate-50 dark:focus-within:bg-slate-800 dark:hover:bg-slate-800 text-black dark:text-white'
60 to={searchQuery.href}
61 >
62 <Activity
63 size={12}
64 className='fill-slate-400 dark:fill-slate-600 mr-3 group-hover:animate-pulse group-hover:scale-125'
65 />
66 <span>{searchQuery.name}</span>
67 </Link>
68 </li>
69 );
70 })}
71 </ul>
72 </div>
73 </div>
74 );
75 };
76 export default DefaultSearchForm;

And search result component:

search-result.tsx

ts
1 import * as React from 'react';
2
3 import { Link } from 'gatsby';
4 import { useStats, Index, Hits, Highlight, Snippet } from 'react-instantsearch';
5 import { Activity } from 'lucide-react';
6
7 import { searchIndices } from '../../../../lib/algolia-queries';
8
9 const SearchResult = () => {
10 const { nbHits } = useStats();
11
12 return (
13 <div className='space-y-4 px-2 py-4'>
14 <div>
15 <ul>
16 {searchIndices.map(({ name, title }, idx) => (
17 <Index indexName={name} key={name} className='py-0'>
18 <div className='flex justify-between my-2'>
19 <span className='px-2 font-semibold uppercase text-slate-700 dark:text-slate-200 text-lg'>
20 {title}
21 </span>
22 {idx == 0 && nbHits > 0 && (
23 <span className='text-slate-600 dark:text-slate-400 flex justify-end text-lg'>
24 {nbHits} post{nbHits !== 1 ? 's' : ''} found
25 </span>
26 )}
27 </div>
28 <Hits
29 hitComponent={({ hit }) => (
30 <div>
31 <Link
32 className='group flex items-center rounded px-2 py-1 text-sm leading-6 outline-none focus-within:bg-slate-50 hover:bg-slate-50 dark:focus-within:bg-slate-800 dark:hover:bg-slate-800 text-black dark:text-white'
33 to={`/${hit.permalink}`}
34 >
35 <Activity
36 size={12}
37 className='fill-slate-400 dark:fill-slate-600 mr-3 group-hover:animate-pulse group-hover:scale-125'
38 />
39 <Highlight attribute='title' hit={hit} className='text-xl' />
40 </Link>
41 <Snippet
42 attribute='excerpt'
43 hit={hit}
44 className='text-slate-600 dark:text-slate-400'
45 />
46 </div>
47 )}
48 />
49 </Index>
50 ))}
51 </ul>
52 </div>
53 </div>
54 );
55 };
56 export default SearchResult;

Now connect those components in search-form.tsx

tsx
1 import * as React from 'react';
2 import { Form, useForm } from 'react-hook-form';
3 import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
4 import * as ScrollArea from '@radix-ui/react-scroll-area';
5
6 import { SearchFormProps } from './search-form.props';
7 import SearchResult from './_components/search-result';
8 import DefaultSearchForm from './_components/default-search-form';
9 import useSearchStore from '../../../store/searchStore';
10 import { Input } from '@/components/ui/input';
11 import {
12 FormControl,
13 FormDescription,
14 FormField,
15 FormItem,
16 FormLabel,
17 FormMessage,
18 } from '@/components/ui/form';
19 import { SearchIcon } from 'lucide-react';
20 import { useSearchBox } from 'react-instantsearch';
21
22 type SearchInput = {
23 searchQuery: string;
24 };
25
26 const SearchForm = () => {
27 const { query, refine } = useSearchBox();
28 const searchTerms = useSearchStore((state) => state.searchTerms);
29 const addSearchTerm = useSearchStore((state) => state.addSearchTerm);
30 const { register, handleSubmit } = useForm<SearchInput>({
31 defaultValues: {
32 searchQuery: '',
33 },
34 mode: 'onChange',
35 });
36
37 const onChange = ({ searchQuery = '' }: SearchInput) => {
38 refine(searchQuery);
39 const isSame = Array.from(searchTerms.entries()).find(
40 ([number, term]) => term.includes(searchQuery) || term.startsWith(searchQuery),
41 );
42 if (!isSame && searchQuery?.length > 4) {
43 addSearchTerm(searchQuery);
44 }
45 };
46
47 return (
48 <>
49 <form onChange={handleSubmit(onChange)} className='flex items-center relative'>
50 <VisuallyHidden.Root>
51 <label htmlFor='search-modal'>Search</label>
52 </VisuallyHidden.Root>
53 <SearchIcon className='absolute left-2.5 top-2 h-4 w-4 text-muted-foreground' />
54 <Input
55 {...register('searchQuery')}
56 placeholder='search'
57 className='pl-8 h-8 mr-8 focus-visible:ring-offset-1 focus-visible:ring-1'
58 type='search'
59 placeholder='Search'
60 aria-label='Search'
61 defaultValue={query}
62 />
63 </form>
64 <ScrollArea.Root className='max-h-[calc(85vh-44px)]'>
65 <ScrollArea.Viewport className='h-full w-full'>
66 {query && query.length > 1 ? <SearchResult /> : <DefaultSearchForm refine={refine} />}
67 </ScrollArea.Viewport>
68 <ScrollArea.Scrollbar
69 className='flex h-full w-2 touch-none select-none border-l border-l-transparent p-[1px] transition-colors'
70 orientation='vertical'
71 >
72 <ScrollArea.Thumb className='relative flex-1 rounded-full bg-slate-300' />
73 </ScrollArea.Scrollbar>
74 <ScrollArea.Scrollbar
75 className='flex h-2.5 touch-none select-none flex-col border-t border-t-transparent p-[1px] transition-colors'
76 orientation='horizontal'
77 >
78 <ScrollArea.Thumb className='relative flex-1 rounded-full bg-slate-300' />
79 </ScrollArea.Scrollbar>
80 <ScrollArea.Corner className='bg-blackA5' />
81 </ScrollArea.Root>
82 </>
83 );
84 };
85 export default SearchForm;

Last one step - create a index in a search dir:

tsx
1 // index.tsx
2
3 import * as React from 'react';
4 import { useState, useEffect } from 'react';
5 import { Link } from 'gatsby';
6
7 import { InstantSearch, useSearchBox } from 'react-instantsearch';
8 import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
9 import { Search as SearchIcon } from 'lucide-react';
10
11 import { Input } from '../ui/input';
12 import HeaderSearchBlock from './blocks/header-search.block';
13 import SearchForm from './forms/search-form';
14 import {
15 Dialog,
16 DialogClose,
17 DialogContent,
18 DialogDescription,
19 DialogOverlay,
20 DialogPortal,
21 DialogTitle,
22 DialogTrigger,
23 } from '../ui/dialog';
24 import { Button } from '../ui/button';
25 import { algoliasearch } from 'algoliasearch';
26 import { searchIndices } from '../../lib/algolia-queries';
27
28 type SearchProps = {
29 isOpen: boolean;
30 setIsOpen: (value: boolean) => void;
31 };
32
33 const Search = ({ isOpen, setIsOpen }: SearchProps) => {
34 const searchClient = React.useMemo(() => algoliasearch(`ALGOLIA_APP_ID`, `ALGOLIA_API_KEY`), []);
35
36 useEffect(() => {
37 const handleKeyDown = (event: KeyboardEvent) => {
38 if (event.key === '/' && !isOpen) {
39 event.preventDefault();
40 setIsOpen(true);
41 }
42 if (event.metaKey && event.key === 'k') {
43 setIsOpen(true);
44 }
45 };
46
47 window.addEventListener('keydown', handleKeyDown);
48
49 return () => {
50 window.removeEventListener('keydown', handleKeyDown);
51 };
52 }, [isOpen]);
53
54 return (
55 <Dialog open={isOpen} onOpenChange={setIsOpen}>
56 <DialogTrigger className='relative'>
57 <SearchIcon className='absolute left-2.5 top-2 h-4 w-4 text-muted-foreground' />
58 <Input
59 type='search'
60 placeholder='Search...'
61 className='pl-8 h-8 focus-visible:ring-offset-1 focus-visible:ring-1'
62 />
63 </DialogTrigger>
64 <DialogPortal>
65 <DialogOverlay className='data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/20 dark:bg-black/70' />
66 <DialogContent className='data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid max-h-[85vh] w-[90vw] max-w-3xl translate-x-[-50%] translate-y-[-50%] overflow-hidden border bg-slate-100 dark:bg-slate-700 shadow-lg duration-300 sm:rounded'>
67 <VisuallyHidden.Root>
68 <DialogTitle>Search</DialogTitle>
69 <DialogDescription>Start typing to search the documentation</DialogDescription>
70 </VisuallyHidden.Root>
71 <React.Suspense
72 fallback={<div class='h-6 w-6 animate-spin rounded-full border-b-2 border-current' />}
73 >
74 <InstantSearch searchClient={searchClient} indexName={searchIndices[0].name}>
75 <SearchForm />
76 </InstantSearch>
77 </React.Suspense>
78 </DialogContent>
79 </DialogPortal>
80 </Dialog>
81 );
82 };
83
84 export default Search;

Connect the search component to index page

Remove button and put search component

tsx
1 // index.tsx
2
3 import * as React from 'react';
4 import type { HeadFC, PageProps } from 'gatsby';
5 import { Button } from '../components/ui/button';
6 import Search from '../components/search';
7
8 const IndexPage: React.FC<PageProps> = () => {
9 return (
10 <main>
11 <Search />
12 </main>
13 );
14 };
15
16 export default IndexPage;
17
18 export const Head: HeadFC = () => <title>Home Page</title>;

Try to search algolia

Algolia search

Conclusion

By combining Algolia's robust search capabilities, React Hook Form's form management, and ShadcnUI's elegant styling, you can easily implement a powerful, responsive, and user-friendly search experience for your Gatsby site. This setup offers great scalability and performance, ensuring users can quickly and efficiently find content.

JavaScript Development Space

© 2024 JavaScript Development Space - Master JS and NodeJS. All rights reserved.