Search
Updated over a week ago

Overview

πŸ‘ Heads up

  • If you are using Starter Kit, there's no need need to manually create the Sections and Components below.

  • All the code examples below are just an implementation suggestions. For brevity, the styles are omitted.

  • For this guide, we will be creating Sections and Components locally. Make sure you have your Local Development Environment ready.

πŸ‘‰ Remember to commit and push newly created Sections and Components to your remote repository to sync the changes with Shogun Frontend.

Shogun Frontend's solution for Site Search uses Algolia, a third party AI-powered search and discovery platform. By integrating Algolia within Shogun Frontend's ecosystem, any CMS Content Group is searchable.

This documentation will provide an overview of how to create and integrate Search into your store.

The outline below illustrates how the data flows from your store to Algolia:

  • Models: a representation of a CMS Content Group, for example: "products" or "blog." The Fields, such as "name" and "description," are the data of that model that will be used to be searched against when a customer submits their search query. Currently, only text fields can be configured as searchable fields.

  • Selected fields: data that Algolia will return to the Results page.

πŸ“˜ To ensure that Algolia's Site Search is properly configured for your store, it's crucial to have your CMS Content Groups (models and selected fields) provided to Shogun.

The below use case will provide context on how to identify this required information:

πŸ’‘ "When my customers searches for 'tire', I want Shogun Frontend's Search to look into my store's products (model) > name (fields) and return a list of matches containing product's name, slug, media and price (selected fields)".

Use as few fields as possible, cause max total size of searchable data per CMS Content Groups is 100 KB.

Adding Search to your store

The client side of the Search will be divided into two parts:

  1. a text input where the customer will enter their search queries

  2. a results page

1 - Creating SearchQueryInput component

It is most common to have an input for search in the Header of your application.

To create an input component:

  1. On your local environment, create a new folder inside src/components and name it as SearchQueryInput.

  2. Create an index.js file inside the SearchQueryInput folder.

  3. Create a styles.module.css file inside the SearchQueryInput folder.

The SearchQueryInput will receive the search query from the customer, and after submission, navigate to the Search section with the search query set as URL parameter.

// SearchQueryInput component
import * as React from 'react'
import {
Input,
InputGroup,
InputRightElement,
FormControl,
useMultiStyleConfig,
} from '@chakra-ui/react'
import Icon from 'Components/Icon'
import IconButton from 'Components/IconButton'
import { normalizePropValue } from 'Components/Utils'

const SearchQueryInput = React.forwardRef((props, ref) => {
let {
variant,
size,
initialValue = '',
disabled,
placeholder = 'Search',
icon = <Icon icon="SearchIcon" />,
onSearchSubmit,
} = props

size = normalizePropValue(size)
variant = normalizePropValue(variant)
const styles = useMultiStyleConfig('SearchQueryInput', { size, variant })
// Local state to handle the customer's search query
const [query, setQuery] = React.useState(initialValue)

return (
<form
onSubmit={e => {
e.preventDefault()
onSearchSubmit(query)
}}
>
<FormControl>
<InputGroup sx={styles.inputGroup} size={size}>
<Input
sx={styles.input}
ref={ref}
size={size}
aria-label="Search"
name="query"
value={query}
disabled={disabled}
placeholder={placeholder}
required
onChange={e => setQuery(e.target.value)}
/>
<InputRightElement sx={styles.inputRightElement} size={size}>
<IconButton
sx={styles.iconButton}
size="full"
type="submit"
aria-label="Submit search query"
disabled={query.length === 0 || disabled}
icon={icon}
/>
</InputRightElement>
</InputGroup>
</FormControl>
</form>
)
})

export default SearchQueryInput

The SearchQueryInput can now be added into the Header section:

import * as React from 'react'
import SearchQueryInput from 'Components/SearchQueryInput'
import { useRouter } from 'frontend-router'

const Header = () => {
const router = useRouter()

const handleSearchSubmit = React.useCallback(query => router.push(`/search?q=${query}`), [router])

return (
<Grid
as="header"
...
>
<SearchQueryInput onSearchSubmit={handleSearchSubmit} />
</Grid>
)
}

export default Header

2 - The results page

The search section will read the search query a customer has entered into the SearchQueryInput using the useSearch hook from the frontend-ui package and display the search results.

πŸ“˜ It's important to note that typo tolerance enabled by default.

2.1 - useSearch hook

The frontend-ui package provides access to the useSearch hook, allowing easy integration of Algolia search into our stores.

The useSearch hooks accept a parameter to control how many items the result page will include.

useSearch({ hitsPerPage: 10 })

After calling it, useSearch will return an object containing the following keys:

name

type

description

status

`'PRISTINE'

'IDLE'

statuses

{ PRISTINE: 'PRISTINE', IDLE: 'IDLE', ERRORED: 'ERRORED', LOADING: 'LOADING', FETCHING_MORE: 'FETCHING_MORE' }

Constants representing different search states

errorMessage

`string

null`

hits

Array<{ objectID: string }>

Array of matching object IDs

pagination

{ page: number, totalPages: number, totalHits: number }

Current pagination state

search

async (query, type = 'Products') => Promise

Callback to trigger search operation. Not passing the type parameter will default to searching on 'Products'. You can pass the parameter to search on a different model instead (for example 'Product Collections').

fetchMore

async () => Promise

Callback to fetch next page of search results

2.2 - Creating the Search section

The search section will read the search query a customer has entered into the SearchQueryInput and display the search results.

Creating the Search Section:

  1. On your local environment, create a new folder inside src/sections and name it as Search.

  2. Create an index.js file inside the Search folder.

  3. Create a styles.module.css file inside the Search folder.

// Search section
import * as React from 'react'
import Flex from 'Components/Flex'
import { useRouter } from 'frontend-router'
import { useSearch } from 'frontend-ui'
import ProductGrid from 'Components/ProductGrid'
import SearchQueryInput from 'Components/SearchQueryInput'
import Divider from 'Components/Divider'
import Container from 'Components/Container'
import Button from 'Components/Button'
import Text from 'Components/Text'

const HITS_PER_PAGE = process.env.NODE_ENV === 'development' ? 3 : 10
const Search = () => {
const router = useRouter()
const { q: searchQuery } = router.query
const {
status,
statuses: { ERRORED, LOADING, PRISTINE, FETCHING_MORE },
errorMessage,
hits,
search,
fetchMore,
pagination: { page, totalPages },
} = useSearch({
hitsPerPage: HITS_PER_PAGE,
})

React.useEffect(() => {
if (!searchQuery) return
search(searchQuery)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchQuery])

return (
<Container p={{ base: 2, md: 8 }}>
<Flex align="center" justify="center" my={10}>
<Container flexBasis={{ base: '90vw', md: 'md' }}>
<SearchQueryInput
initialValue={searchQuery}
disabled={status === LOADING || status === FETCHING_MORE}
onSearchSubmit={q => router.push(`/search?q=${q}`)}
/>
</Container>
</Flex>

<Divider />

<Container my={10}>
{hits.length === 0 && status === PRISTINE && <Text>Start searching to see results</Text>}

{hits.length === 0 && status !== PRISTINE && status !== LOADING && (
<Text>No results found for {searchQuery}</Text>
)}

{hits.length === 0 && status === LOADING && <Text>Loading</Text>}

{status === ERRORED && <Text>Error {errorMessage}</Text>}

{hits.length > 0 && (
<React.Fragment>
<ProductGrid
collection={{
name: 'Search results',
slug: 'search-results',
descriptionHtml: '',
products: hits,
}}
/>

{totalPages > page + 1 && (
<Container textAlign="center" my={10}>
<Button onClick={() => fetchMore()}>
{status === FETCHING_MORE ? '...' : 'Load more'}
</Button>
</Container>
)}
</React.Fragment>
)}
</Container>
</Container>
)
}

export default Search

This Section won’t receive any props, so variables are not created.

2.3 - Search page

A new page must be created to display the search results.

To create a page:

  1. Click on the Pages icon from the sidebar.

  2. Click ADD PAGE on the top right corner.

  3. Enter the PAGE NAME, "Search".

  4. Click SAVE PAGE.

Next, add the Search section:

  1. Navigate to the Experience Manager (XM) by clicking the Pages icon on the sidebar.

  2. Search for "Search".

  3. Open the "Search" by clicking the title.

  4. On the sidebar, click on the + (plus sign) where it says "Add sections to start building your page".

  5. Search for and select Search from the list of all Sections shown.

  6. Ensure the page is marked Ready to Publish

Your customers will now be able to search for any item in your store.

Did this answer your question?