Overview

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)".

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. Open any Section.
  2. Click on Components.
  3. Click on the + (plus sign) button to create a new Component.
  4. The IDE should be refreshed with a sample code in it.
  5. Name your Component by clicking on the pencil icon button in the top left corner of your screen. When complete, click the checkmark icon to save the name.
  6. Click SAVE to save the Component.

If you are using the Starter Kit, you can use the components and sections below out of the box.

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' | 'ERRORED' | 'LOADING' | 'FETCHING_MORE'

Status of performed search operation

statuses

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

Constants representing different search states

errorMessage

string | null

Error message if something went wrong

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

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. Click on the Sections icon on the sidebar.
  2. Click ADD SECTION on the top right corner.
  3. Enter the name for the Section; we will name it Search.
  4. Click SAVE SECTION.
// 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 page help you?