Skip to main content

Overview

Contensis React Base (CRB) provides a complete search system backed by Contensis's Elasticsearch implementation. React Starter gives you a boilerplate configuration layer on top of it, plus hooks for connecting React components to search state.

Architecture​

Search in React Starter is built on three layers:

  1. Config — defines what to search for (content types, facets, listings, minilists) via search.config.ts
  2. Mappers — transform raw Delivery API results into the shape your components need
  3. Hooks — useFacets, useListing, and useMinilist from CRB connect React components to search Redux state

What the app owns vs what CRB owns​

CRB provides all search Redux state, sagas, and action creators. Never create your own search reducers, sagas, or action type constants — they will conflict with CRB at runtime.

Your app only provides:

FilePurpose
src/app/search/search.config.tsDeclares facets, listings, and minilists with their query parameters
src/app/search/search.transformations.tsThe SearchTransformations object passed to all hooks
src/app/search/searchResults.mapper.tsMaps raw CMS Entry[] to typed component props
src/app/search/searchResultsInformation.mapper.tsMaps Redux state to result counts and status text
src/app/schema/search.schema.tsString key constants for all facets, listings, and minilists
src/app/routes/withEvents.tsWires searchConfig into CRB via onRouteLoaded
src/app/templates/<name>/Page template that calls useFacets or useListing
src/app/search/minilists/<name>.minilist.tsWrapper hook around useMinilist

The three search contexts​

ContextHookConfig keyRoute bindingUse case
FacetuseFacetssearchConfig.facetssearchOptions.facetMulti-tab search UI with facet switching; URL-bound state
ListinguseListingsearchConfig.listingssearchOptions.listingTypeDedicated listing/category page; supports text search and filters
MinilistuseMinilistsearchConfig.minilistNone — no route neededEmbedded results widget; local state, not URL-bound

Wiring search to routes​

Search state is dynamically injected when a search route loads. This happens in withEvents.ts via onRouteLoaded:

src/app/routes/withEvents.ts
import searchTransformations from '~/search/search.transformations';
import { searchConfig } from '~/search/search.config';

onRouteLoaded: function* onRouteLoaded({ params }) {
return yield {
searchOptions: {
config: searchConfig, // passes ALL facets, listings, and minilists
mappers: searchTransformations, // required — omitting this silently breaks result mapping
params,
},
};
},
caution

This must be in onRouteLoaded, not onRouteLoad. Using the wrong lifecycle hook means search Redux is never injected and hooks will receive no data. Omitting mappers is a silent failure — results will render without the title/URI/description mapping.

Schema constants​

Always define facet, listing, and minilist keys as constants in src/app/schema/search.schema.ts. Never use string literals directly in routes or config:

src/app/schema/search.schema.ts
export const facets = {
all: 'all',
news: 'news',
};

export const listings = {
plants: 'plants',
};

export const minilists = {
relatedArticles: 'relatedArticles',
};

export const freeTextWeights = {
title: 10,
description: 5,
body: 1,
};

Use these constants in routes and config:

// ✅ Correct — refactor-safe
{ path: '/search', searchOptions: { facet: facets.all } }

// ❌ Wrong — typo-prone, silently broken if the key changes
{ path: '/search', searchOptions: { facet: 'all' } }

baseFields and whereSysUri​

Two patterns are required in every facet, listing, and minilist config:

baseFields — always start your field projection with baseFields from src/app/schema/fields.schema.ts. This ensures every result has the minimum fields needed for a result card (entryTitle, entryDescription, entryThumbnail, sys.id, sys.uri, etc.):

import { baseFields } from '~/schema/fields.schema';

queryParams: {
fields: [...baseFields, 'author', 'category'], // always spread baseFields first
}

whereSysUri — always include this clause in customWhere to exclude non-routable entries (drafts, config entries, etc.) from results:

const whereSysUri = { field: 'sys.uri', exists: true };

queryParams: {
customWhere: [whereSysUri],
}

Next steps​

  • Config — full SearchQueryParams reference and annotated example
  • Mappers — how to write searchResults.mapper.ts and searchResultsInformation.mapper.ts
  • Filters — customWhere query syntax and SearchFilter UI filters
  • useFacets — hook API and full template skeleton
  • useListing — hook API and full template skeleton
  • useMinilist — embedded widget hook
  • Advanced — tabs, compositions, featured results, i18n