@pdftron/react-ab
v1.1.0
Published
AB testing suite for PDFTron React websites
Downloads
1,309
Readme
PDFTron React AB Testing
AB testing tools for React projects.
Features
- 🚀 Integrates with any AB testing provider
- 🔄 Hot swappable backends means you you switch AB providers without rewriting all your code
- 🔰 Easy to use API and React components
- ⚠️ Robust error handling and fallback cases
- 🚦Variant forcing for testing and debugging
- 📟 SSR support
- 💥 Much more!
Table of contents
- Installation
- Usage
- Documentation
Installation
yarn add @pdftron/react-ab
Also requires React and React DOM as peer dependencies.
Usage
First, you need to register your experiments with the ABService
. This should happen as soon as possible in your application.
import { ABService } from '@pdftron/react-ab';
ABService.registerExperiments({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
});
Next, you must wrap your entire app in the Provider component, and provide a backend for it to use.
import { ABService, ABProvider } from '@pdftron/react-ab';
import GoogleOptimizeBackend from './backend'
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
});
export default () => {
return (
<ABProvider backend={GoogleOptimizeBackend}>
<App />
</ABProvider>
)
}
Now, anywhere in your app, you can set up an AB test with the ABTest
and Variant
components:
import { ABTest, Variant } from '@pdftron/react-ab';
export const Button = ({text}) => {
return (
<ABTest name='buttonTest'>
<Variant variant={0}>
<button style={{color: 'blue'}}>
{text}
</button>
</Variant>
<Variant variant={1}>
<button style={{color: 'red'}}>
{text}
</button>
</Variant>
</ABTest>
)
}
The ABTest
component will render whichever variant your backend specifies. In this scenerio, if the backend returns 0
, then it will render a blue button, if it returns 1
it will render a red button.
Experiments
An "experiment" is an object that specifies the experiment name, its ID, and how many variants it should have. There are also a few other useful options you can set.
To register a test, call ABService.registerExperiments
and pass in an object containing your experiments. The key is the name of the experiment, and the value is data describing that experiment.
ABService.registerExperiments({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
});
Each experiment contains the following data:
id
(string) the ID of the experiment. This will be passed into your backendsgetVariant
function.variantCount
(number) how many variants this experiment is expected to have.testingId
(number - optional) A unique ID/Index that is used purely for testing. See testing for more details
Backends
A "backend" in this context is just an object that implements an interface that will be used by the component libarary.
A backend must satisfy the following interface:
type Backend = {
getVariant: (experimentId: string) => Promise<number>;
setVariant?: (experimentId: string, variant: number) => void;
}
The getVariant
function accepts an experiment ID, and must return which variant of that experiment to use. This is where you would call your AB providers API to get a variant.
The setVariant
function is used for the SSR feature. It is only required if SSR is enabled.
Example
This example uses Google Optimize and GTAG to get a variant index, but you could use any provider you wish.
const GoogleOptimizeBackend = {
getVariant: (experimentId) => {
return new Promise(resolve => {
gtag('event', 'optimize.callback', {
name: experimentId,
callback: (value) => {
resolve(Number(value));
}
})
})
}
}
Components
ABProvider
This is a high level component that must wrap all instances of ABTest
component. It should be at the very top of your application.
Props
backend
(Backend) the backend to use for your experimentschildren
(ReactNode) React children to renderssrVariants
(Object) See the SSR Guide for more info
Example
import { ABProvider } from '@pdftron/react-ab';
const backend = {
getVariant: async (expId) => {
const result = await fetch(`http://my-provider.com/api/get-variant?id=${expId}`);
const json = await result.json();
const { variantIndex } = json;
return variantIndex;
}
}
export const Root = () => {
return (
<ABProvider backend={backend}>
<App />
</ABProvider>
)
}
ABTest
This is a wrapper component that is the heart of your AB tests. It accepts a set of Variants, and renders only one depending on what index your getVariant
function returns.
Props
name
(string) the name of the experiement that this represents. The experiment must be registered before you can use it. This should equal the key of the experiment that you pass toregisterExperiments
. See experiments for more info.children
(Variant[]) The variants for this experimentloadingComponent
(ReactNode - optional) A placeholder component to render while the experiment is loading. By default, nothing is rendered.
Example
import { ABTest, Variant } from '@pdftron/react-ab'
export default () => {
return (
<ABTest name='experiment-name' loadingComponent={<div>Loading...</div>}>
<Variant variant={0}>
<p>This is rendered if `0` is returned from your backends `getVariant` function</p>
</Variant>
<Variant variant={1}>
<p>This is rendered if `1` is returned from your backends `getVariant` function</p>
</Variant>
</ABTest>
)
}
Variant
This component represents a single or multiple variants for your experiment. The children of this component are only rendered if the variant
is chosen.
Props
variant
(number | number[]) the index of the variant. If the result ofgetVariant
matches this number, then the children will be rendered. If you pass an array of numbers, it will render the children if the selected variant mathces any number in the arraychildren
(ReactNode) the children to render if this variant is selecteddescription
(string - optional) a description of the variant, used purely for readability
Example
import { ABTest, Variant } from '@pdftron/react-ab'
export default () => {
return (
<ABTest name='experiment-name' loadingComponent={<div>Loading...</div>}>
<Variant variant={0} description="Original variant">
<p>Rendered if variant 0 is selected</p>
</Variant>
<Variant variant={[1,2,3]} description="Rendered as an h1">
<h1>Rendered if variant 1, 2, or 3 is selected</h1>
</Variant>
<Variant variant={4} description="Rendered as an h2">
<h2>Rendered if variant 4 is selected</h2>
</Variant>
</ABTest>
)
}
PageSwitch
This is a utility component that only renders components on certain pages. It is not directly related to AB testing, but is useful in scenerios where you want to AB test parts of a global component (header, footer), but only on a certiain page.
This component is similar to a JS router.
Props
pathname
(string) The current pathname. Can be fetched fromwindow.location.pathname
, or from your JS router.children
(Page[]) An array of pages.
Example
This example sets up an AB test that changes the text of a header. However, this test will only run when the user is on /contact-sales
. If the user is not on that page, it will default to the Page
that has isDefault
set.
import { PageSwitch, Page } from '@pdftron/react-ab';
export const Footer = () => {
return (
<div>
<ul>
<li>Home</li>
<li>Docs</li>
<PageSwitch pathname={window.location.pathname}>
<Page path='/contact-sales'>
<ABTest name='sales-button'>
<Variant variant={0}>
<li>Free trial</li>
</Variant>
<Variant variant={1}>
<li>Get Started</li>
</Variant>
</ABTest>
</Page>
<Page isDefault>
<li>Free trial</li>
</Page>
</PageSwitch>
</ul>
</div>
)
}
Page
A component that gets rendered inside PageSwitch
Props
Note: One of path
or isDefault
is required.
path
(string | string[] - optional) Only render this component if the pathname passed intoPageSwitch
matches this value or one of these valuesisDefault
(boolean - optional) Render this component if no other Page components have a matching page.children
(ReactNode) The children to render
See PageSwitch for an example.
Error handling
In the case of an error, the components will fall back to rendering the original variant (variant 0).
The following scenerios will cause the component to go into fallback mode:
- An error is thrown from your backend
- A variant index is returned from your backend that does not exist
- The call to
getVariant
on your backend times out. See timeouts for more info. - AB testing is disabled
Timeouts
By default, if your backend takes more than 1500 to return a variant from the getVariant
function, the component will go into fallback mode and default to variant 0.
To change this timeout, you can call ABService.setVariantTimeout(ms: number)
and pass a new value for this timeout (in ms).
For example, to change the timeout to 3 seconds, you would do
import { ABService } from '@pdftron/react-ab';
ABService.setVariantTimeout(3000)
Disable AB testing
In some scenerios, you may want to disable AB testing for the whole app, but you might not want to strip out all your AB code. One common scenerio of this is if the users AdBlock extension blocks your AB provider code from being loaded.
In this scenerio, you can disable AB testing by calling ABService.disable()
. This will set the AB suite into fallback mode, and variant 0
will always be rendered.
import { ABService } from '@pdftron/react-ab';
ABService.disable()
Testing
Forcing variants
You can test your experiments by setting query parameters that force an experiment to use a certain variant.
To force override an experiment, you must provide two query params:
force
(number) ThetestingId
of the experiment to override. See experiments for more info.variant
(number) The variant index to force
Example
Consider the following configuration
import {ABProvider, ABService, ABTest, Variant} from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2,
testingId: 1
}
});
// Somewhere else in app
export default () => {
return (
<ABTest name='buttonTest'>
<Variant variant={0}>
<p>V1</p>
</Variant>
<Variant variant={1}>
<p>V2</p>
</Variant>
</ABTest>
)
}
To force variant zero, you could navigate to http://localhost:8000?force=1&variant=0
To force variant one, you could navigate to http://localhost:8000?force=1&variant=1
Enable logging
To enable debug logging, call ABService.enableLogging()
import { ABService } from '@pdftron/react-ab';
ABService.enableLogging()
SSR
If your application is server rendered, you may want to select and render your variant on the server in order to prevent content flashes when your site loads.
To enable server side rendering, please follow these steps:
- Pass
enableSSR
to yourregisterExperiments
call
import { ABService } from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
- Pass a variant map you want to select to the
ABProvider
A variant map is just a object where the keys are the experiment name, and the value is the variant you want to select.
We also provide a utility function to handle this for you if you do not want to implement yourself.
import { ABService, ABProvider } from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
export const Root = () => {
return (
<ABProvider
backend={backend}
ssrVariants={{
buttonTest: 1
}}
>
<App />
</ABProvider>
)
}
- Add a
setVariant
function to your backend (optional)
This function is used to report to your AB Testing service which variant you rendered.
Example using Google Optimize:
const GoogleOptimizeBackend = {
getVariant: (experimentId) => {
// getVariant code
},
setVariant: (experimentId, variant) => {
ga('set', 'exp', `${experimentId}.${variant}`);
}
}
SSR utility
We provide a utility function to generate a variant map for you. It looks at all your registered experiments and outputs a map. It also handles client side hydration and cookies.
ABService.getSSRVariants(cookies: Record<string, any>, setCookie: (name: string, value: number) => void)
Params:
cookies
(object) A map of cookies in the request. Used for making sure the user always sees the same variantssetCookie
(function) A function that is called whenever a new cookie is generated and needs to be set. It is passed the cookie name and the cookie value.
Returns a map of cookies names -> variant indexes.
Example (in the context of Next.js):
import { ABService, ABProvider } from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
export const Root = ({ variants }) => {
return (
<ABProvider
backend={backend}
ssrVariants={variants}
>
<App />
</ABProvider>
)
}
export const getServerSideProps = async (context) => {
const variants = ABService.getSSRVariants(
context.req.cookies,
(name, value) => {
context.req.cookies.set(name, value)
}
)
return {
props: {
variants
},
};
};