Rendering Experiences

Using the Ninetailed SDK to render Experiments and Personalizations.

The <Experience> Component

The Ninetailed React-based SDKs provide an <Experience> component to wrap your existing React components you use to render your content. This is the most declarative way to render Ninetailed personalization and experiment content, and therefore the methodology that most Ninetailed users should adopt when able.

The <Experience> component functions wraps your existing React component. It automatically detects the properties needed from the wrapped component. The experiences prop accepts Ninetailed Experience content that has been appropriately transformed by the ExperienceMapper available from our Utility Libraries.

<Experience> Component Props

PropDescription

id

[Required] The CMS entry ID of the baseline

component

[Required] The React component that your baseline and variants will use to render. This can either be regular React component or a component that opts into React's forwardRef. See the Tracking Impressions of Experiences section for details.

experiences

[Required] An array of experience CMS entries mapped using the ExperienceMapper methods available from our Utility Libraries

{...baseline}

[Required] Any and all props that the function passed as the component prop needs to receive to render the baseline variant entry. This will depend entirely on the structure of your existing React component(s).

passthroughProps

[Optional] An object containing key-value pairs of props that should be sent to the component irrespective of which experience variant is selected. Props supplied here will overwrite those of the selected variant, so this is designed for non-content props like state or refs.

loadingComponent

[Optional] A custom component to show prior to the <Experience> component selecting a variant. This defaults to a transparent version of your component using the baseline props.

Example Use

These examples show working CMS data, our Utility Libraries, and the <Experience> component together in demonstrative examples. Your implementation will vary according to your existing React components and your data source. Consult the Utility Libraries documentation to know what data to fetch from your Content Source and how to transform the returned Experience entries to the format required by the <Experience> component.

These examples show fetching CMS data from within potentially deeply nested React components. In practice, you will likely fetch that data from higher within your rendering tree and pass it to components, especially when statically pre-rendering. However, the mapping exercises and use of the <Experience> component demonstrated remain the same no matter what rendering strategy you adopt.

YourExperience.(jsx|tsx)
// or '@ninetailed/experience.js-next', '@ninetailed/experience.js-gatsby'
import { Experience } from '@ninetailed/experience.js-react';
import { ExperienceMapper } from '@ninetailed/experience.js-utils'

// This function is assumed to return a single entry and all its supporting data, including referenced content, in their entirety
import { getCmsEntry } from '../api/yourEntryGetter'
import { YourComponent } from './YourComponent'

export const YourExperience = (cmsBaselineEntry) => {
  const baselineEntry = getCmsEntry(cmsBaselineEntry);
  const experiences = baselineEntry['nt_experiences']
  
  const mappedExperiences = (experiences || [])
  .map((experience) => {
    return {
      id: experience.id,
      name: experience.name
      type: experience.nt_type as 'nt_personalization' | 'nt_experiment'
      config: experience.nt_config,
      audience: {
        id: experience.nt_audience.nt_audience_id
      },
      variants: experience.variants.map((variant) => {
        return {
          id: variant.id, // Required
          // Map any other data from the variant required by your component
          ...variant,
          someComponentProp: variant.foo
        }
      })
    }
  })
  .filter((experience) => ExperienceMapper.isExperienceEntry(experience))
  .map((experience) => ExperienceMapper.mapExperience(experience));
  
  return (
    <Experience
      id={entry.id} // Required. The id of the BASELINE entry
      component={YourComponent} // Required. What to use to render the selected variant
      {...baselineEntry} // Any props your `component` above needs
      experiences={mappedExperiences} // Array of mapped experiences
  />);
};

Inline Personalization with Merge Tags

Ninetailed allows you to embed content placeholders into Rich Text Fields that can then be rendered client-side using information from the current visitor's profile. These dynamic placeholder entries are called Merge Tags, which can then be used as inline entries within a rich text field of your CMS entries.

The React-based SDKs provide a corresponding <MergeTag /> component that allow you to declaratively render the inlined Merge Tag entries.

While rendering Merge Tag entries embedded within Rich Text Fields is the most common use for merge tags, you simply pass the property accessor (using dot notation) of any Ninetailed profile property as the id of the MergeTag component.

components/RichText.js
import React from 'react';
import { INLINES } from '@contentful/rich-text-types';
import { documentToReactComponents} from '@contentful/rich-text-react-renderer';
// or `@ninetailed/experience.js-next', @ninetailed/experience.js-gatsby'
import { MergeTag } from '@ninetailed/experience.js-react';

export const renderRichText = (richTextDocument) => {
  return documentToReactComponents(richTextDocument, {
    renderNode: {
      [INLINES.EMBEDDED_ENTRY]: (node) => {
        if (node.data.target.sys.contentType.sys.id === 'nt_mergetag')) {
          return (
            <MergeTag 
              id={node.data.target.fields.nt_mergetag_id}
              fallback={node.data.target.fields.nt_fallback}
            />
          );
        }
      }
    },
  });
};

Tracking Impressions of Experiences

The <Experience> component needs to understand when the markup it renders is present within the visitor's viewport, because this is the criteria used to fire impression events to any connected Ninetailed plugins. By default, the <Experience> component does this by inserting an empty non-displaying <div> of class nt-cmp-marker immediately prior to the rendered component.

// Returned markup from the <Experience> component when passing a regular component
<div className="nt-cmp-marker" style="display: none !important">
<YourPassedComponent {...propsFromExperienceComponent} />

However, you may also pass a forwardRef component to the component prop, allowing you control what DOM element's presence in the viewport should trigger these impression events. See the React forwardRef documentation for more details.

Last updated