Use the Atomic React Wrapper

The integration of JSX in React projects with Atomic web components can be tricky. Atomic React is a wrapper around the core Atomic library meant to address this issue.

Since Atomic React is built on top of the core Atomic component library, most concepts that apply to the core Atomic library apply directly to Atomic React. The goal of this article is to go over the few areas where the use of Atomic React differs from the use of the core Atomic library.

Tip

For a concrete example you may want to start from or refer to throughout this article, see this Atomic React page.

Install Atomic React

NPM

Install Atomic React using the npm package.

npm i @coveo/atomic-react

CDN

The library is also available via the Coveo CDN as an IIFE (Immediately Invoked Function Expression).

We recommend this approach only for quick prototyping and testing, as it requires very minimal front-end tooling and build tools. We recommend against using the IIFE approach in production. Browsers have to download the entire library code, regardless of which components are actually used. We rather recommend using a bundler in this situation (for example Webpack).

You can read more about this approach below.

Static Assets

For performance reasons, the generated Atomic React JavaScript bundle does not automatically include static assets that are loaded on demand. This impacts language support, as well as the use of included SVG icons.

You must make available external assets distributed with Atomic React by including them in the public directory of your app. Without this, you’ll face various issues. For example, labels in the app will appear as temporary placeholders.

The location of the public directory depends on how you build, configure and distribute your app. For example, for any project created with Create React App, this would mean copying language and icon assets to the ./public directory.

cp -r node_modules/@coveo/atomic-react/dist/assets public/assets
cp -r node_modules/@coveo/atomic-react/dist/lang public/lang
Note

Be sure to respect the folder hierarchy, with SVG icons under the assets subdirectory and labels and languages under the lang subdirectory of the public folder.

Result Templates

The way to define result templates for an HTML project using the core Atomic library involves defining one or multiple atomic-result-template components, configured with HTML properties, adding conditions on the attributes and metadata of each result.

Coupled with the <template> HTML tag, this approach works very well in a plain HTML project. However, it can be limiting and awkward to use in a React project using JSX.

Atomic React exposes atomic-folded-result-list, atomic-result-list and atomic-search-box-instant-results components with a template property that can be used in a more straightforward manner when coupled with JSX. The template property accepts a function with a Result parameter in the case of the atomic-result-list and atomic-search-box-instant-results components, and a FoldedResult in the case of the atomic-folded-result-list component. Use those parameters to conditionally render different templates based on properties and fields available in result items. The template function must then simply return a valid JSX Element.

The following is an example of how to create a fictitious search page with predefined templates for YouTube videos and Salesforce cases:

const MyResultTemplateForYouTubeVideos: React.FC<{result: Result}> = ({
  result,
}) => {
  return (
    <>
      <AtomicResultSectionVisual>
        <AtomicResultImage field="ytthumbnailurl" />
      </AtomicResultSectionVisual>
      <AtomicResultSectionTitle>
        <AtomicResultLink />
      </AtomicResultSectionTitle>
      {result.raw.ytvideoduration !== undefined && (
        <AtomicResultSectionBottomMetadata>
          <AtomicText value="Duration" />
          <AtomicResultNumber field="ytvideoduration">
            <AtomicFormatUnit unit="minute" />
          </AtomicResultNumber>
        </AtomicResultSectionBottomMetadata>
      )}
    </>
  );
};
 
const MyResultTemplateForSalesforceCases: React.FC<{result: Result}> = ({
  result,
}) => {
  return (
    <>
      <AtomicResultSectionTitle>
        <AtomicResultLink />
      </AtomicResultSectionTitle>
      <AtomicResultSectionExcerpt>
        <AtomicResultText field="excerpt" />
      </AtomicResultSectionExcerpt>
      <AtomicResultSectionEmphasized>
        {result.raw.sfpriority !== undefined && (
          <>
            <AtomicText value="Priority" />
            <AtomicResultText field="sfpriority" />
          </>
        )}
        {result.raw.sfstatus !== undefined && (
          <>
            <AtomicText value="Status" />
            <AtomicResultText field="sfstatus" />
          </>
        )}
      </AtomicResultSectionEmphasized>
    </>
  );
};
 
const MyDefaultTemplate: React.FC<{}> = () => {
  return (
    <>
      <AtomicResultSectionTitle>
        <AtomicResultLink />
      </AtomicResultSectionTitle>
      <AtomicResultSectionExcerpt>
        <AtomicResultText field="excerpt" />
      </AtomicResultSectionExcerpt>
    </>
  );
};
 
const MyResultTemplateFunction = (result: Result) => {
  if (result.raw.filetype === 'YoutubeVideo') {
    return <MyResultTemplateForYouTubeVideos result={result} />;
  }
 
  if (result.raw.objecttype === 'Case') {
    return <MyResultTemplateForSalesforceCases result={result} />;
  }
 
  return <MyDefaultTemplate />;
};
 
const MyPage = () => {
  const engine = buildSearchEngine({
    configuration: getSampleSearchEngineConfiguration(),
  });
  return (
    <AtomicSearchInterface engine={engine}>
      <AtomicResultList template={MyResultTemplateFunction} /> 1
    </AtomicSearchInterface>
  );
};
1 Use the template property of the atomic-result-list component to invoke your result template function in your search interface.

Result Template Component Styling

Due to the way Atomic web components use Shadow Dom and CSS parts to provide encapsulation, you must follow the guidelines below to style elements inside any result template.

Higher-Order Components

This approach consists in wrapping any core Atomic component inside a styled one, which you can then reuse in one or more result templates. You can do so by using inline styling as shown in the example below, or using more advanced techniques such as CSS modules.

This option works well if you don’t have to create any CSS rule that would target the shadow parts of an Atomic result component.

The following example sets the color of all result links in a template to pink.

const MyStyledResultLink: React.FC<
  React.ComponentProps<typeof AtomicResultLink> 1
> = (props) => {
  return (
    <AtomicResultLink {...props} style={{color: 'pink'}}>
      {props.children}
    </AtomicResultLink>
  );
};
 
const MyPage = () => {
  const engine = buildSearchEngine({
    configuration: getSampleSearchEngineConfiguration(),
  });
  return (
    <AtomicSearchInterface engine={engine}>
      <AtomicResultList
        template={(result) => { 2
          return <MyStyledResultLink />;
        }}
      />
    </AtomicSearchInterface>
  );
};
1 This line allows you to extract all props exposed by the AtomicResultLink component and to reuse them in your higher-order component. You could also extend those props.
2 Use the template property of the atomic-result-list component to display your higher-order component.

<style> Tag

Using <style> tags works in all scenarios, and allows you to target any Shadow parts that an Atomic result component exposes, similarly to what you would do using plain HTML.

The following is an example that sets the text color of an AtomicResultBadge to pink:

const myStyles = `
atomic-result-badge::part(result-badge-element) {
    color: pink;
}
`;
 
const MyPage = () => {
  const engine = buildSearchEngine({
    configuration: getSampleSearchEngineConfiguration(),
  });
  return (
    <AtomicSearchInterface engine={engine}>
      <AtomicResultList template={(result)=> { 1
          <style>{myStyles}</style>
          <AtomicResultBadge />
        }} />
    </AtomicSearchInterface>
  );
};
1 Use the template property of the AtomicResultList component to display your styled component.

Localization (i18n)

The Atomic React search interface component exposes an optional localization option, which takes a callback function that lets you handle localization.

<AtomicSearchInterface
  localization={(i18n) => {
    i18n.addResourceBundle('en', 'translation', {
      search: "I'm feeling lucky!",
    });
  }}
></AtomicSearchInterface>

Usage with CDN Scripts (IIFE)

To use the IIFE approach, you need to pull different scripts in the page in the correct order, using proper external dependencies with matching versions.

First, identify the version of @coveo/atomic that is used by @coveo-atomic-react.

You can do that by running npm view @coveo/atomic-react, and then identifying the @coveo/atomic dependency:

> npm view @coveo/atomic-react


@coveo/atomic-react@1.23.11 | Proprietary | deps: 1 | versions: 209
React specific wrapper for the Atomic component library
https://github.com/coveo/ui-kit#readme

dist
.tarball: https://registry.npmjs.org/@coveo/atomic-react/-/atomic-react-1.23.11.tgz
.shasum: 64ba0a5f686e3638b1180910f6fed5e4980bd9e2
.integrity: sha512-/qI5O7SWNinBYWAedRW62ko0oF4c/hfapgqKe9xSq/NJfg+KBzcR3SbdyhpaUAWpPX1910GP9cMyr4wf8irJnQ==
.unpackedSize: 771.1 kB

dependencies:
@coveo/atomic: 1.108.2

In the above example, you can see that at the time of this writing, the current latest version of @coveo/atomic-react is 1.23.11, and that it is using @coveo/atomic at version 1.108.2.

Then, you need to do the same to find the matching version of @coveo/headless used by the target @coveo/atomic version.

Using the above example with @coveo/atomic@1.108.2:

> npm view @coveo/atomic@1.108.2


@coveo/atomic@1.108.2 | Apache-2.0 | deps: 15 | versions: 728
A web-component library for building modern UIs interfacing with the Coveo platform
https://docs.coveo.com/en/atomic/latest/

dist
.tarball: https://registry.npmjs.org/@coveo/atomic/-/atomic-1.108.2.tgz
.shasum: 6e844073f55f10328b4b828c0190b8fe31403fbf
.integrity: sha512-LhFR6k7NdGi8pYHmWOq3gM3rE0zeTKc6biTa1Z+ZKr5TEXIr97rpxi1HSnViQUowQYYCcY44dvsAYz2yyPvXTA==
.unpackedSize: 87.1 MB

dependencies:
@coveo/bueno: 0.42.1                  @stencil/store: 1.5.0                 focus-visible: 5.2.0
@coveo/headless: 1.103.3              coveo-styleguide: 9.34.4              i18next-http-backend: 1.4.1
@popperjs/core: ^2.11.6               dayjs: 1.11.5                         i18next: 20.6.1
@salesforce-ux/design-system: ^2.16.1 dompurify: 2.3.10                     stencil-inline-svg: 1.1.0
@stencil/core: 2.17.3                 escape-html: 1.0.3                    ts-debounce: ^4.0.0

In the above example, you can see that @coveo/atomic@1.108.2 has the dependency @coveo/headless@1.103.3.

To summarize, as of the time of this writing, the versions to keep in mind are the following:

  • @coveo/atomic-react → 1.23.11

  • @coveo/atomic → 1.108.2

  • @coveo/headless → 1.103.3

This means you would import Atomic React as follows:

<head>
  <!-- React and ReactDOM need to be included  -->
  <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

  <!-- Optional script, which allows to use JSX directly in an inline script in the page -->
  <script crossorigin src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <!-- @coveo/headless needs to be included as a dependency -->
  <!-- Note the matching major and minor versions as explained above -->
  <script crossorigin src="https://static.cloud.coveo.com/headless/v1.103/headless.js"></script>


  <!-- @coveo/atomic needs to be included as a dependency -->
  <!-- Note the matching major and minor versions as explained above -->
  <script crossorigin type="module" src="https://static.cloud.coveo.com/atomic/v1.108/atomic.esm.js"></script>

  <!--And then finally @coveo/atomic-react is included -->
  <!-- Note the matching major and minor versions as explained above -->
  <script crossorigin src="https://static.cloud.coveo.com/atomic-react/v1.23/iife/atomic-react.min.js"></script>
</head>

Once this is done, you can start using CoveoAtomicReact directly with an inline script tag:

<!DOCTYPE html>
<html>
  <head>
    <!-- ... -->
  </head>
  <body>
    <div id="container"></div>
  </body>
  <script type="text/babel">
    'use strict';
    class SearchPage extends React.Component {
      constructor(props) {
        super(props);
        // Configure engine
        this.engine = CoveoAtomicReact.buildSearchEngine({...});
      }
      render() {
        return (
          <CoveoAtomicReact.AtomicSearchInterface engine={this.engine}>
            <CoveoAtomicReact.AtomicSearchBox></CoveoAtomicReact.AtomicSearchBox>
            <CoveoAtomicReact.AtomicResultList
              template={MyTemplateFunction}
            ></CoveoAtomicReact.AtomicResultList>
            <!-- ... -->
          </CoveoAtomicReact.AtomicSearchInterface>
        );
      }
    }
    const domContainer = document.querySelector('#container');
    const root = ReactDOM.createRoot(domContainer);
    root.render(React.createElement(SearchPage));
  </script>
</html>

Reference

All components available in the core Atomic library are available in the Atomic React wrapper. Additionally, the following Atomic React components expose options not available in the equivalent core Atomic components.

atomic-search-interface

In addition to the properties and methods available in the core atomic-search-interface component, the Atomic React atomic-search-interface component exposes the following properties:

Property Attribute Description Type Default

localization

localization

An optional callback that lets you control the interface localization. The function receives the search interface i18n instance, which you can then modify (see Localization).

(i18n: i18n) ⇒ void

onReady

on-ready

An optional callback function that can be used to control the execution of the first query.

(executeFirstSearch: ExecuteSearch) ⇒ Promise<void>

If not provided, a default function will be used, which executes the first query immediately after initialization.

theme

theme

An optional theme property can be set in order to load one of the premade Coveo themes. Possible values are:

  • coveo: the default theme, used if no value is provided. It consists in a set of colors that match the Coveo brand.

  • accessible: a high contrast theme, best suited for implementations where web accessibility is important.

  • none: no premade theme will be loaded. You will have to provide the theme yourself.

string

'none'

atomic-folded-result-list

In addition to the properties and methods available in the core atomic-folded-result-list component, the Atomic React atomic-folded-result-list component exposes the following property:

Property Attribute Description Type Default

template

template

A template function that takes a folded result item and outputs its target rendering as a JSX element. It can be used to conditionally render different types of result templates based on the properties of each result.

(result: FoldedResult) ⇒ JSX.Element

atomic-result-list

In addition to the properties and methods available in the core atomic-result-list component, the Atomic React atomic-search-interface component exposes the following property:

Property Attribute Description Type Default

template

template

A template function that takes a result item and outputs its target rendering as a JSX element. It can be used to conditionally render different types of result templates based on the properties of each result.

(result: Result) ⇒ JSX.Element

atomic-search-box-instant-results

In addition to the properties and methods available in the core atomic-search-box-instant-results component, the Atomic React atomic-search-box-instant-results component exposes the following property:

Property Attribute Description Type Default

template

template

A template function that takes a result item and outputs its target rendering as a JSX element. It can be used to conditionally render different types of result templates based on the properties of each result.

(result: Result) ⇒ JSX.Element