Comment on page
Rendering Experiences
Using the Ninetailed SDK to render Experiments and Personalizations.
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.Prop | Description |
---|---|
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 |
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. |
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.General JavaScript Utils Library
Contentful Utils Library
YourExperience.(jsx|tsx)
1
// or '@ninetailed/experience.js-next', '@ninetailed/experience.js-gatsby'
2
import { Experience } from '@ninetailed/experience.js-react';
3
import { ExperienceMapper } from '@ninetailed/experience.js-utils'
4
5
// This function is assumed to return a single entry and all its supporting data, including referenced content, in their entirety
6
import { getCmsEntry } from '../api/yourEntryGetter'
7
import { YourComponent } from './YourComponent'
8
9
export const YourExperience = (cmsBaselineEntry) => {
10
const baselineEntry = getCmsEntry(cmsBaselineEntry);
11
const experiences = baselineEntry['nt_experiences']
12
13
const mappedExperiences = (experiences || [])
14
.map((experience) => {
15
return {
16
id: experience.id,
17
name: experience.name
18
type: experience.nt_type as 'nt_personalization' | 'nt_experiment'
19
config: experience.nt_config,
20
audience: {
21
id: experience.nt_audience.nt_audience_id
22
},
23
variants: experience.variants.map((variant) => {
24
return {
25
id: variant.id, // Required
26
// Map any other fields required by your component
27
...variant,
28
someComponentProp: variant.foo
29
}
30
})
31
}
32
})
33
.filter((experience) => ExperienceMapper.isExperienceEntry(experience))
34
.map((experience) => ExperienceMapper.mapExperience(experience));
35
36
return (
37
<Experience
38
id={entry.id} // Required. The id of the BASELINE entry
39
component={YourComponent} // Required. What to use to render the selected variant
40
{...baselineEntry} // Any props your `component` above needs
41
experiences={mappedExperiences} // Array of mapped experiences
42
/>);
43
};
YourExperience.(jsx|tsx)
1
// or '@ninetailed/experience.js-next', '@ninetailed/experience.js-gatsby'
2
import { Experience } from '@ninetailed/experience.js-react';
3
4
// For use with Contentful REST APIs only
5
import { ExperienceMapper } from '@ninetailed/experience.js-utils-contentful'
6
7
// This function is assumed to return a single entry and all its nested references from the REST Contentful CDA in their entirety
8
import { getContentfulEntry } from '../api/yourEntryGetter'
9
import { YourComponent } from './YourComponent'
10
11
export const YourExperience = (cmsBaselineEntry) => {
12
const baselineEntry = getContentfulEntry(cmsBaselineEntry);
13
const experiences = baselineEntry.fields.nt_experiences;
14
const mappedExperiences = experiences
15
.filter((experience) => ExperienceMapper.isExperienceEntry(experience))
16
.map((experience) => ExperienceMapper.mapExperience(experience))
17
18
return (
19
<Experience
20
id={baselineEntry.sys.id} // Required. The sys.id of the BASELINE entry
21
component={YourComponent} // Required. What to use to render the selected variant
22
{...baselineEntry} // Any props your `component` above needs
23
experiences={mappedExperiences} // Array of mapped experiences
24
/>);
25
};
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.Contentful
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} />;
}
}
},
});
};
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.import React from 'react';
// or `@ninetailed/experience.js-next', @ninetailed/experience.js-gatsby'
import { MergeTag } from '@ninetailed/experience.js-react';
const Greeting = () => {
return (
<>
// This will render nothing if the firstName trait is not populated
<p>Welcome back, <MergeTag id="traits.firstName" />
<p>How is the weather in <MergeTag id="location.city" />?</p>
</>
};
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 modified 8d ago