AB testing suite for PDFTron React websites
npm install @pdftron/react-abAB testing tools for React projects.
- 🚀 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!
- Installation
- Usage
- Documentation
- Experiments
- Backends
- Components
- ABProvider
- ABTest
- Variant
- PageSwitch
- Page
- Error handling
- Timeouts
- Disable AB testing
- Testing & Debugging
- Forcing variants
- Logging
- SSR
yarn add @pdftron/react-ab
Also requires React and React DOM as peer dependencies.
First, you need to register your experiments with the ABService. This should happen as soon as possible in your application.
``tsx
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.
`tsx
import { ABService, ABProvider } from '@pdftron/react-ab';
import GoogleOptimizeBackend from './backend'
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
});
export default () => {
return (
)
}
`
Now, anywhere in your app, you can set up an AB test with the ABTest and Variant components:
`tsx
import { ABTest, Variant } from '@pdftron/react-ab';
export const Button = ({text}) => {
return (
)
}
`
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.
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.
`ts`
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 backends getVariant 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
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:
`ts`
type Backend = {
getVariant: (experimentId: string) => Promise
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.
`ts`
const GoogleOptimizeBackend = {
getVariant: (experimentId) => {
return new Promise(resolve => {
gtag('event', 'optimize.callback', {
name: experimentId,
callback: (value) => {
resolve(Number(value));
}
})
})
}
}
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 experiments
- children (ReactNode) React children to render
- ssrVariants (Object) See the SSR Guide for more info
Example
`tsx
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 (
)
}
`
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 to registerExperiments. 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
`tsx
import { ABTest, Variant } from '@pdftron/react-ab'
export default () => {
return (
This is rendered if 0 is returned from your backends getVariant function
This is rendered if 1 is returned from your backends getVariant function
$3
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 of getVariant 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 array
- children (ReactNode) the children to render if this variant is selected
- description (string - optional) a description of the variant, used purely for readabilityExample
`tsx
import { ABTest, Variant } from '@pdftron/react-ab'export default () => {
return (
Loading...
Rendered if variant 0 is selected
$3
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 from window.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.`tsx
import { PageSwitch, Page } from '@pdftron/react-ab';export const Footer = () => {
return (
- Home
- Docs
- Free trial
- Get Started
- Free trial
)
}
`$3
PageSwitch#### Props
Note: One of
path or isDefault is required.-
path (string | string[] - optional) Only render this component if the pathname passed into PageSwitch matches this value or one of these values
- isDefault (boolean - optional) Render this component if no other Page components have a matching page.
- children (ReactNode) The children to renderSee 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 disabledTimeouts
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
`ts
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.`ts
import { ABService } from '@pdftron/react-ab';ABService.disable()
`Testing
$3
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) The testingId of the experiment to override. See experiments for more info.
- variant (number) The variant index to forceExample
Consider the following configuration
`tsx
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 (
V1
V2
)
}
`To force variant zero, you could navigate to
http://localhost:8000?force=1&variant=0To force variant one, you could navigate to
http://localhost:8000?force=1&variant=1$3
To enable debug logging, call
ABService.enableLogging()`ts
import { ABService } from '@pdftron/react-ab';ABService.enableLogging()
`$3
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:
1) Pass
enableSSR to your registerExperiments call`js
import { ABService } from '@pdftron/react-ab';ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
`2) Pass a variant map you want to select to the
ABProviderA 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.
`js
import { ABService, ABProvider } from '@pdftron/react-ab';ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
export const Root = () => {
return (
backend={backend}
ssrVariants={{
buttonTest: 1
}}
>
)
}
`3) 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:
`js
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: RecordParams:
-
cookies (object) A map of cookies in the request. Used for making sure the user always sees the same variants
- setCookie (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):
`js
import { ABService, ABProvider } from '@pdftron/react-ab';ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
export const Root = ({ variants }) => {
return (
backend={backend}
ssrVariants={variants}
>
)
}
export const getServerSideProps = async (context) => {
const variants = ABService.getSSRVariants(
context.req.cookies,
(name, value) => {
context.req.cookies.set(name, value)
}
)
return {
props: {
variants
},
};
};
``