This guide provides detailed information about our React SDK. This library is built on top of our regular JavaScript SDK to ease the integration in React applications by providing a set of components with their equivalent HOCs, as well as helper functions based on React hooks API, so you can interact with the underneath SDK and work towards any use cases. All of our SDKs are open source. Go to our React SDK GitHub repository to see the source code.
Language support
This SDK requires React 16.3.0 or above, since it uses React Context API.
Some features, such as useClient
and useManager
, use React Hooks API that requires React 16.8.0 or later. Also remember to follow the rules of React hooks when using these. When it comes to browser support, our CDN bundle supports IE11+ and all the other major browsers.
For server-side rendering with React use the Node.js SDK.
React SDK is not meant for server-side rendering. It uses the JavaScript SDK API for browsers. Thus, using it for SSR, with the ReactDOMServer API or frameworks such as Next.js or Gatsby, might lead to unexpected behaviours. You can use Node.js SDK instead.
If you're interested in using the functionality and components of React SDK on the server side please reach out to us at support@split.io.
Initialization
Set up Split in your code base in two steps.
1. Import the SDK into your project
You can import the SDK into your project using one of the following three methods:
<!-- Don't forget to include React script tags before Split SDK. More details at https://reactjs.org/docs/add-react-to-a-website.html#step-2-add-the-script-tags -->
<script src="//cdn.split.io/sdk/splitio-react-1.8.3.min.js"></script>
npm install --save @splitsoftware/splitio-react@1.8.3
yarn add @splitsoftware/splitio-react@1.8.3
2. Instantiate the SDK and create a new Split client
You can use either the component or the High Order Component (HOC). All the components exposed by this library have an equivalent HOC.
// CDN will expose the library globally with the "splitio" variable. Use the options that work best with your code.
const { SplitFactory, withSplitFactory } = window.splitio;
// Create the config for the SDK factory.
const sdkConfig = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
// key represents your internal user id, or the account id that
// the user belongs to.
// This could also be a cookie you generate for anonymous users.
key: 'key'
}
};
// Using the SplitFactory component, MyApp component and all its descendants have access to the SDK functionality.
const App = () => (
<SplitFactory config={sdkConfig} >
<MyApp />
</SplitFactory>
);
// As an alternative, you can use the equivalent withSplitFactory HOC, wrapping the given component inside a Split factory.
const App = withSplitFactory(sdkConfig)(MyApp);
// The npm package exposes the different components and functions of the library as named exports.
const { SplitFactory, withSplitFactory } = require('@splitsoftware/splitio-react');
// Create the config for the SDK factory.
const sdkConfig = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
// key represents your internal user id, or the account id that
// the user belongs to.
// This could also be a cookie you generate for anonymous users.
key: 'key'
}
};
// Using the SplitFactory component, MyApp component and all its descendants have access to the SDK functionality.
const App = () => (
<SplitFactory config={sdkConfig} >
<MyApp />
</SplitFactory>
);
// As an alternative, you can use the equivalent withSplitFactory HOC, wrapping the given component inside a Split factory.
const App = withSplitFactory(sdkConfig)(MyApp);
// The npm package exposes the different components and functions of the library as named exports.
import { SplitFactory, withSplitFactory } from '@splitsoftware/splitio-react';
// Create the config for the SDK factory.
const sdkConfig: SplitIO.IBrowserSettings = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
// key represents your internal user id, or the account id that
// the user belongs to.
// This could also be a cookie you generate for anonymous users.
key: 'key'
}
};
// Using the SplitFactory component, MyApp component and all its descendants have access to the SDK functionality.
const App: React.ComponentType = () => (
<SplitFactory config={sdkConfig} >
<MyApp />
</SplitFactory>
);
// As an alternative, you can use the equivalent withSplitFactory HOC, wrapping the given component inside a Split factory.
const App: React.ComponentType = withSplitFactory(sdkConfig)(MyApp);
For more flexibility as we wanted to support most use cases, the SplitFactory
component can receive the factory already instantiated for our React SDK to use it. If you're using the JavaScript SDK already, it is recommended that you follow the Singleton Pattern and keep only one instance of the factory at all times, which you can provide to the React SDK following the approach shown below.
You could access the JavasScript SDK factory function through the SplitSdk
named export of the React SDK too, which points to the original JavaScript SDK function so you don't need to import two different Split packages.
Note that you should handle the SDK shutdown yourself when providing a factory via the factory
prop, since the SDK doesn't know if it's being used for something else. The SplitFactory
component doesn't shut it down automatically.
Also note that you shouldn't mix the two options, either provide a config (recommended) or provide the factory instance. Otherwise you'll get an error.
// The npm package exposes the different components and functions of the library as named exports.
// If you were using the bundle via CDN, it'll be at `window.splitio.SplitSdk`
import { SplitSdk, SplitFactory, withSplitFactory } from '@splitsoftware/splitio-react';
// Create the Split factory object with your custom settings, using the re-exported function.
const factory: SplitIO.ISDK = SplitSdk({
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
key: 'key'
},
...
});
// Using the SplitFactory component, MyApp component and all it's descendants will have access to the SDK functionality using the provided factory.
const App: React.ComponentType = () => (
<SplitFactory factory={factory} >
<MyApp />
</SplitFactory>
);
// As an alternative, you can use the equivalent withSplitFactory HOC where the factory instance would be the 2nd parameter.
const App: React.ComponentType = withSplitFactory(false, factory)(MyApp);
Notice for TypeScript
With the SDK package on NPM, you get the SplitIO namespace, which contains useful types and interfaces for you to use.
Feel free to dive into the declaration files if IntelliSense is not enough!
We recommend keeping only one instance of the factory at all times (singleton pattern) and reusing it throughout your application.
Include the SDK key for the environment that you are setting up the SDK for. The SDK key is available on your Organization Settings page, on the API keys section. Select the "Client-side" type. This is a special type of API token with limited privileges to be used for the untrustable world of browsers or mobile clients. Learn more about API keys.
Using the SDK
Get treatments with configurations
When the SDK is instantiated, it kicks off background tasks to update an in-memory cache with small amounts of data fetched from Split servers. This process can take up to a few hundred milliseconds depending on the size of data. If the SDK is asked to evaluate which treatment to show to a customer for a specific feature flag while its in this intermediate state, it may not have the data necessary to run the evaluation. In this case, the SDK does not fail, rather, it returns the control treatment.
To make sure the SDK is properly loaded before asking it for a treatment, block until the SDK is ready, as shown below. We provide the isReady
boolean prop based on the client that will be used by the component. Internally we listen for the SDK_READY
event triggered by given SDK client to set the value of isReady
.
After the isReady
prop is set to true, you can use the SDK. SplitTreatments
component returns the proper treatments based on the names
prop value passed to it and the key
value you passed in the config when instantiating the SDK. Then use the treatments
prop to access the treatment values as well as the corresponding dynamic configurations that you defined in the Split user interface. Remember to handle the client returning control as a safeguard.
Similarly to the vanilla JS SDK, React SDK supports the ability to evaluate flags based on cached content when using LOCALSTORAGE as storage type. In this case, the isReadyFromCache
prop will change to true almost instantly since access to the cache is synchronous, allowing you to consume flags earlier on components that are critical to your UI. Keep in mind that the data might be stale until isReady
prop is true. Read more below.
import { SplitTreatments } from '@splitsoftware/splitio-react';
import MyComponentV1 from './MyComponentV1';
import MyComponentV2 from './MyComponentV2';
const featureName = 'FEATURE_FLAG_NAME';
/**
* This is one way to write a toggler component, which might be convenient for code cleanup afterwards
* as you remove both toggle and legacy component, then wire the version you'll keep.
* But it's not the only way to use the treatments. Always follow the pattern that works best for you!
**/
export default class MyComponentToggle extends React.Component {
renderContent(treatmentWithConfig) {
const { treatment, config } = treatmentWithConfig;
if (treatment === 'on') return (<MyComponentV2 config={config} {...this.props} />);
return (<MyComponentV1 config={config} {...this.props} />);
}
render() {
return (
<SplitTreatments names={[featureName]} >
{({ treatments, isReady }) => { // Passes down a TreatmentsWithConfig object and SplitContext properties like the boolean `isReady` flag.
return isReady ?
this.renderContent(treatments[featureName]) : // Use the treatments and configs.
<Spinner />; // Render a spinner if the SDK is not ready yet. You can do what you want with this boolean.
}}
</SplitTreatments>
);
}
}
import { SplitTreatments } from '@splitsoftware/splitio-react';
import MyComponentV1 from './MyComponentV1';
import MyComponentV2 from './MyComponentV2';
const featureName: string = 'FEATURE_FLAG_NAME';
/**
* This is just one way to write a toggler component, which might be convenient for code cleanup afterwards
* as you remove both toggle and legacy component, then wire the version you'll keep.
* But it's not the only way to use the treatments. Always follow the pattern that works best for you!
**/
export default class MyComponentToggle extends React.Component {
renderContent(treatmentWithConfig: SplitIO.TreatmentWithConfig) {
const { treatment, config } = treatmentWithConfig;
if (treatment === 'on') return (<MyComponentV2 config={config} {...this.props} />);
return (<MyComponentV1 config={config} {...this.props} />);
}
render() {
return (
<SplitTreatments names={[featureName]} >
{({ treatments, isReady }: ISplitTreatmentsChildProps) => { // Passes down a TreatmentsWithConfig object and SplitContext properties like the boolean `isReady` flag.
return isReady ?
this.renderContent(treatments[featureName]) : // Use the treatments and configs.
<Spinner />; // Render a spinner if the SDK is not ready yet. You can do what you want with this boolean.
}}
</SplitTreatments>
);
}
}
import { withSplitTreatments } from '@splitsoftware/splitio-react';
import MyComponentV1 from './MyComponentV1';
import MyComponentV2 from './MyComponentV2';
const featureName = 'FEATURE_FLAG_NAME';
/**
* This is one way to write a toggler component, which might be convenient for code cleanup afterwards
* as you remove both toggle and legacy component, then wire the version you'll keep.
* But it's not the only way to use the treatments. Always follow the pattern that works best for you!
**/
class MyComponentToggle extends React.Component {
renderContent(treatmentWithConfig) {
const { treatment, config } = treatmentWithConfig;
if (treatment === 'on') return (<MyComponentV2 config={config} {...this.props} />);
return (<MyComponentV1 config={config} {...this.props} />);
}
render() {
// A component wrapped by the `withSplitTreatments` HOC receives a TreatmentsWithConfig object and SplitContext properties as props
const { treatments, isReady } = this.props;
return isReady ?
this.renderContent(treatments[featureName]) : // Use the treatments and configs.
<Spinner />; // Render a spinner if the SDK is not ready yet. You can do what you want with this boolean.
);
}
}
// As an alternative you could use our equivalent HOC
export default withSplitTreatments([featureName])(MyComponentToggle);
import { useContext } from 'react';
import { useTreatments, SplitContext } from '@splitsoftware/splitio-react';
import MyComponentV1 from './MyComponentV1';
import MyComponentV2 from './MyComponentV2';
const featureName = 'FEATURE_FLAG_NAME';
function renderContent(treatmentWithConfig, props) {
const { treatment, config } = treatmentWithConfig;
if (treatment === 'on') return (<MyComponentV2 config={config} {...props} />);
return (<MyComponentV1 config={config} {...props} />);
}
/**
* This is another way to write a toggler with a function component that uses hooks.
**/
function MyComponentToggle (props) {
// access status properties via the SplitContext
const { isReady } = useContext(SplitContext);
// evaluate feature flags with the `useTreatments` hook
const treatments = useTreatments([featureName]);
return isReady ?
renderContent(treatments[featureName], props) : // Use the treatments and configs.
<Spinner />; // Render a spinner if the SDK is not ready yet. You can do what you want with this boolean.
);
}
// As an alternative you could use our equivalent HOC
export default MyComponentToggle;
The treatments property/value returned by this library has the following shape:
type TreatmentWithConfig = {
treatment: string,
config: string | null
};
type TreatmentsWithConfig = {
[featureName: string]: TreatmentWithConfig
};
As you can see from the object structure, the config will be a stringified version of the configuration JSON defined in the Split user interface. If there is no configuration defined for a treatment, the SDK returns null
for the config parameter.
Attribute syntax
To target based on custom attributes, the SDK needs to be passed an attribute map at runtime. In the example below, we are rolling out a feature flag to users. The provided attributes plan_type
, registered_date
, permissions
, paying_customer
, and deal_size
are passed to the underlying getTreatmentsWithConfig
call. These attributes are compared and evaluated against the attributes used in the rollout plan as defined in the Split user interface to decide whether to show the on
or off
treatment to this account. The SDK supports five types of attributes: strings, numbers, dates, booleans, and sets. The proper data type and syntax for each are:
- Strings: Use type String.
- Numbers: Use type Number.
- Dates: Use type Date and express the value in
milliseconds since epoch
.
Note: Milliseconds since epoch is expressed in UTC. If your date or date-time combination is in a different timezone, first convert it to UTC, then transform it to milliseconds since epoch. - Booleans: Use type Boolean.
- Sets: Use type Array.
const attributes = {
// date attributes are handled as `millis since epoch`
registered_date: new Date('YYYY-MM-DDTHH:mm:ss.sssZ').getTime(),
// this string will be compared against a list called `plan_type`
plan_type: 'growth',
// this number will be compared agains a const value called `deal_size`
deal_size: 10000,
// this boolean will be compared against a const value called `paying_customer`
paying_customer: true,
// this array will be compared against a set called `permissions`
permissions: ["read", "write"]
};
/** If you're using the SplitTreatments component, you can use attributes by setting the "attributes" prop. **/
const ComponentWithTreatments = () =>
(<SplitTreatments names={[featureName]} attributes={attributes} >
{({ treatments, isReady }) => {
const treatment = treatments[featureName].treatment;
if (treatment === 'on') {
// do something
} else {
// do something else
}
}}
</SplitTreatments>)
/** If you're using the SplitTreatments HOC, you can use attributes by passing those as the second parameter. **/
const ComponentWithTreatments = withSplitTreatments([featureName], attributes)(
({ treatments, isReady }) => {
const treatment = treatments[featureName].treatment;
if (treatment === 'on') {
// do something
} else {
// do something else
}
});
const attributes: SplitIO.Attributes = {
// date attributes are handled as `millis since epoch`
registered_date: new Date('YYYY-MM-DDTHH:mm:ss.sssZ').getTime(),
// this string will be compared against a list called `plan_type`
plan_type: 'growth',
// this number will be compared agains a const value called `deal_size`
deal_size: 10000,
// this array will be compared against a set called `permissions`
permissions: ['read', 'write']
};
/** If you're using the SplitTreatments component, you can use attributes by setting the "attributes" prop. **/
const ComponentWithTreatments = () =>
(<SplitTreatments names={[featureName]} attributes={attributes} >
{({ treatments, isReady }) => {
const treatment: SplitIO.TreatmentWithConfig = treatments[featureName].treatment;
if (treatment === 'on') {
// do something
} else {
// do something else
}
}}
</SplitTreatments>)
/** If you're using the SplitTreatments HOC, you can use attributes by passing those as the second parameter. **/
const ComponentWithTreatments = withSplitTreatments([featureName], attributes)(
({ treatments, isReady }) => {
const treatment: SplitIO.TreatmentWithConfig = treatments[featureName].treatment;
if (treatment === 'on') {
// do something
} else {
// do something else
}
});
Binding attributes to the client
Attributes can optionally be bound to the SplitFactory or SplitClient components via props. These attributes are stored in memory and used in every evaluation to avoid the need to keep the attribute set accessible through the whole app. When an evaluation is called, for example, with the SplitTreatments component or the useTreatment hook, the attributes provided (if any) at evaluation time are combined with the ones that are already loaded into the SDK memory, with the ones provided at evaluation time taking precedence. This enables those attributes to be overridden or hidden for specific evaluations. Refer to the examples below:
import { SplitFactory, SplitClient } from '@splitsoftware/splitio-react';
// Assuming this is how the user setup the factory:
const sdkConfig = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_API_KEY',
key: 'key'
}
};
const factoryAttributes = {
permissions: ["read", "write"]
};
const clientAttributes = {
permissions: "read"
};
const treatmentAttributes = {
deal_size: 10000
};
const App = () => (
<SplitFactory config={sdkConfig} attributes={factoryAttributes}>
<MyComponentWithAttributes />
</SplitFactory>);
class MyComponentWithAttributes extends React.Component {
render() {
return (
<div>
{
/*
* Using SplitTreatments as a descendant of the SplitFactory with no SplitClient wrapping it,
* uses the client main client binded to the key passed in the config for evaluations.
* and uses the attributes received on factory attributes param
* In this case, the used key for evaluations is nicolas@split.
*/
}
<SplitTreatments names={['USER_FEATURE_FLAG_NAME']} attributes={treatmentAttributes}>
{({ isReady, treatments }) => {
// Do something with the treatments for the user traffic type.
// This evaluation combines SplitFactory component attributes with the ones on SplitTreatment component.
// Then it evaluates with attributes = { permissions: ["read", "write"], deal_size: 10000 }
}}
</SplitTreatments>
{
/*
* To use another client, use the SplitClient component. Keep in mind that
* this client might not be ready yet if it's just being created and still downloading segments
* for the new key, but you can use the isReady property to block until ready.
*/
}
<SplitClient splitKey={this.props.accountId} attributes={clientAttributes} >
<SplitTreatments names={['ACCOUNT_FEATURE_FLAG_NAME', 'ACCOUNT_FEATURE_FLAG_NAME_2']} >
{({ isReady, treatments }) => {
// Do something with the treatments for the account traffic type.
// This evaluation is for another client, so factory attributes stored in main client (factory) will not be taking part.
// In this evaluation, the attributes on treatment are combined with SplitClient component ones.
// The evaluation attributes are {permissions: "read"}
}}
</SplitTreatments>
</SplitClient>
</div>
);
}
}
import { SplitFactory, useClient } from '@splitsoftware/splitio-react';
const factoryAttributes = {
permissions: ["read", "write"]
};
const App = () => (
<SplitFactory config={sdkConfig} attributes={factoryAttributes}>
<MyComponentWithNewSdkClient accountId='nicolas@account' />
</SplitFactory>
);
function MyComponentWithNewSdkClient(props) {
const inContextClient = useClient();
// You can retrieve clients for as many keys as you need, although not it's recommended to create more than you absolutely need to.
// Keep in mind that if it is the first time you retrieve a client, it might not be ready yet.
const accountClient = useClient(this.props.accountId, 'user', {clientAttributes});
if (readyAccountClient) {
// `accountClient` is ready to evaluate treatments as usual. To see more information about the client API, refer to the docs for JavaScript SDK.
// In this evaluation, the attributes on treatment are combined with the ones on useClient
const accountTreatments = readyAccountClient.getTreatments(['ACCOUNT_FEATURE_FLAG_NAME', 'ACCOUNT_FEATURE_FLAG_NAME_2']);
return (<div>{...}</div>);
} else {
// `accountClient` is not ready
return (<div>{...}</div>);
}
}
If SplitFactory
component is created with attributes and it has a SplitClient
component where the evaluation is called, the attributes in SplitFactory
component will not take part on evaluation. If the user wants those attributes to be combined and evaluated, make the evaluation in SplitFactory
component.
Not setting any attributes on SplitFactory
or SplitClient
component works like a clearAttributes
method for that component and removes every attribute stored on the respective client.
Shutdown
If SplitFactory
component is created with a config
prop, it instantiates and shutdowns the SDK factory automatically when the component is created and unmounted respectively. Otherwise, if the component is created passing an SDK factory via the factory
prop, the shutdown must be handled by the user.
Track
Use the client.track
method (here is how to access the client) or useTrack
hook function to record any actions your customers perform. Each action is known as an event
and corresponds to an event type
. Tracking events through one of our SDKs or via the API is the first step to getting experimentation data into Split and allows you to measure the impact of your features on your users' actions and metrics.
Learn more about using track events in feature flags.
In the examples below, you can see that tracking events can take up to four arguments. The proper data type and syntax for each are:
- TRAFFIC_TYPE: The traffic type of the key in the track call. The expected data type is String. You can only pass values that match the names of traffic types that you have defined in your instance of Split.
- EVENT_TYPE: The event type that this event should correspond to. The expected data type is String. Full requirements on this argument are:
- Contains 63 characters or fewer.
- Starts with a letter or number.
- Contains only letters, numbers, hyphen, underscore, or period.
- This is the regular expression we use to validate the value:
[a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}
- VALUE: (Optional) The value to be used in creating the metric. This field can be sent in as null or 0 if you intend to purely use the count function when creating a metric. The expected data type is Integer or Float.
- PROPERTIES: (Optional) An object of key value pairs that can be used to filter your metrics. Learn more about event property capture. Split currently supports three types of properties: strings, numbers, and booleans.
The underlying track
method returns a boolean value of true
or false
to indicate whether or not the SDK was able to successfully queue the event to be sent back to Split's servers on the next event post. The SDK returns false
if the current queue size is equal to the config set by eventsQueueSize
config or if an incorrect input has been provided.
In case a bad input is provided, you can read more about our SDK's expected behavior.
Remember that for using the useTrack
hook, you must follow React Hook rules. Therefore, in contexts where they are not allowed, like inside class-based React components, you must track events accessing the Sdk client directly, as shown in the examples below:
import { useTrack } from '@splitsoftware/splitio-react';
function MyComponent() {
// With the useTrack hook you can get access to the regular .track() method of the client.
const track = useTrack();
// If you need to track events for a different key or just want to make sure you're tracking for the right key.
const otherTrack = useTrack('key');
// Here's what the track function signature looks like:
// track(trafficType: string, eventType: string, value?: number, properties?: Properties): boolean;
// Now you can track your events by passing at least the traffic type and the event type.
let queued = track('user', 'logged_in');
// Example with both a value and properties
const properties = { package : "premium", admin : true, discount : 50 };
queued = track('user', 'page_load_time', 83.334, properties);
// If the event doesn't have a value but do have properties, skip the value by passing it null.
queued = track('user', 'logged_in', null, properties);
// Alternatively, you can also bind the trafficType to the track method for convenience by either calling useTrack with a traffic type:
const track = useTrack('key', 'user');
// Or by passing the traffic type to the SDK config when initializing, so the default track function will be already binded.
// NOTE: Providing the traffic type in the config is only recommended if you won't track events for more than one traffic type for the given key and want to save passing the parameter.
const track = useTrack();
// Now you can call the tracker function as always without the need to provide a traffic type.
// Signature of the function would be:
// track(eventType: string, value?: number, properties?: Properties): boolean;
// Same examples as before but now we don't need the traffic type.
queued = track('logged_in');
queued = track('page_load_time', 83.334, properties);
queued = track('logged_in', null, properties);
import { useTrack } from '@splitsoftware/splitio-react';
function MyComponent() {
// With the useTrack hook you can get access to the regular .track() method of the client, with the following signature
let track: (trafficType: string, eventType: string, value?: number, properties?: SplitIO.Properties): boolean = useTrack();
// If you need to track events for a different key or just want to make sure you're tracking for the right key
track = useTrack('key');
// Now you can track your events by passing at least the traffic type and the event type.
let queued: boolean = track('user', 'logged_in');
// Example with both a value and properties
const properties: SplitIO.Properties = { package : "premium", admin : true, discount : 50 };
queued = track('user', 'page_load_time', 83.334, properties);
// If the event doesn't have a value but does have properties, skip the value by passing it null.
queued = track('user', 'logged_in', null, properties);
// Alternatively, you can also bind the trafficType to the track method for convenience by either calling useTrack with a traffic type:
let otherTrack: (eventType: string, value?: number, properties?: SplitIO.Properties): boolean = useTrack('key', 'user');
// Or by passing the traffic type to the SDK config when initializing, so the default track function will be already binded.
// NOTE: Providing the traffic type in the config is only recommended if you won't track events for more than one traffic type for the given key and want to save passing the parameter.
otherTrack = useTrack();
// Same examples as before but now we don't need the traffic type
queued = otherTrack('logged_in');
queued = otherTrack('page_load_time', 83.334, properties);
queued = otherTrack('logged_in', null, properties);
import { SplitClient, SplitFactory, SplitTreatments } from '@splitsoftware/splitio-react';
// you can access the SDK client as a render prop passed down by the `SplitFactory` component
const App = () => (
<SplitFactory config={localstorageConfig} >
{({ isReady, factory, client }) => {
// `client` is the Sdk client which key (or user id) is defined in the Sdk config
let queued = client.track('user', 'logged_in');
// this is equivalent to:
queue = factory.client().track('user', 'logged_in');
...
}}
</SplitFactory>
);
// you can also access the SDK client passed down by the `SplitTreatments` and `SplitClient` components
const App = () => {
return (
<SplitFactory config={localstorageConfig} >
<SplitTreatments names={['some-split']}>
{({ isReady, factory, client }) => {
// `client` is the main client, i.e., `factory.client()`
const queued = client.track('user', 'logged_in');
...
}}
</SplitTreatments>
<SplitClient splitKey='key' >
{({ isReady, factory, client }) => {
// `client` here is the new client with 'key' user id
const queued = client.track('user', 'logged_in');
...
}}
</SplitClient>
<SplitClient splitKey='key2' trafficType='user'>
{({ isReady, factory, client }) => {
// `client` is another client with 'key2' as user id and binded traffic type
const queued = client.track('logged_in');
...
}}
</SplitClient>
</SplitFactory>
)
}
// The same can be done using HOCs, such as `withSplitFactory`:
import { withSplitFactory } from '@splitsoftware/splitio-react';
const App = withSplitFactory(localstorageConfig)(
({ isReady, factory, client }) => {
const queued = client.track('user', 'logged_in');
...
}
);
Configuration
The SDK has a number of knobs for configuring performance. Each knob is tuned to a reasonable default. However, you can override the value while providing the config to the SplitFactory as shown in the Initialization section of this doc. To learn about the available configuration options, go to the JavaScript SDK Configuration section.
Localhost mode
For testing, a developer can put code behind feature flags on their development machine without the SDK requiring network connectivity. To do this, start the Split SDK in localhost mode (also called off-the-grid mode). In this mode, the SDK neither polls or updates from Split servers. Instead, it uses an in-memory data structure to determine what treatments to show to the logged in customer for each of the features.
When instantiating the SDK in localhost mode, your authorizationKey
is "localhost"
. Define the features you want to use in the features
object map. All SplitTreatments
components and useTreatments
calls for a feature flag return the treatment (and config, if defined) you have defined in the map. You can then change the treatment as necessary for your testing. If you want to update a treatment or a config, or add or remove feature flags from the mock cache, update the features object map you've provided. The SDK simulates polling for changes and update from it.
Any feature that is not provided in the features
map returns the control treatment if the SDK is asked to evaluate them. Use the following additional configuration parameters when instantiating the SDK in localhost
mode:
Configuration | Description | Default value |
---|---|---|
scheduler.offlineRefreshRate | The refresh interval for the mocked features treatments. | 15 |
features | A fixed mapping of which treatment to show for our mocked features. | {} By default we have no mocked features. |
To use the SDK in localhost mode, replace the SDK Key with "localhost", as shown in the following test examples. Note that you can define the object between a feature flag name and treatment directly or use a map to define both a treatment and a dynamic configuration.
If you define just a string as the value for a feature flag name, the config returned by the SDK is null. If you use a map, we return the specified treatment and the specified config, which can also be null.
import React from "react";
// React testing library: https://www.npmjs.com/package/@testing-library/react
import { render, waitFor } from "@testing-library/react";
import { SplitFactory, SplitTreatments } from "@splitsoftware/splitio-react";
const config = {
core: {
authorizationKey: 'localhost',
key: 'user_id'
},
features: {
'reporting_v2': 'on', // example with just a string value for the treatment
'billing_updates': { treatment: 'visa', config: '{ "color": "blue" }' }, //example of a defined config
'show_status_bar': { treatment: 'off', config: null } // example of a null config
}
};
const MyApp = () => {
return (
<SplitFactory config={config} >
<SplitTreatments names={['reporting_v2']} >
{({ isReady, treatments }) => {
return <div>{`${isReady ? 'SDK ready.' : 'SDK not ready.'} Feature flag reporting_v2 is ${treatments['reporting_v2'].treatment}`}</div>;
}}
</SplitTreatments>
</SplitFactory>
);
};
describe('MyApp', () => {
test('renders the correct treatment', async () => {
const { getByText, findByText } = render(<MyApp/>);
// On the initial rendered output, the SDK is not ready. So treatment values are control.
expect(getByText('SDK not ready. Feature flag reporting_v2 is control')).toBeTruthy();
// In localhost mode, the SDK is ready and the component re-rendered with the mocked treatment on next event-loop tick.
// So we use `findByText` to wait for the component to update.
expect(await findByText('SDK ready. Feature flag reporting_v2 is on')).toBeTruthy();
// `waitFor` (https://testing-library.com/docs/dom-testing-library/api-async/#waitfor) can also be used:
expect(await waitFor(() => getByText('SDK ready. Feature flag reporting_v2 is on')));
});
});
import React from 'react';
// Enzyme testing utility: https://www.npmjs.com/package/enzyme
import { mount } from 'enzyme';
import { SplitFactory, SplitTreatments } from '@splitsoftware/splitio-react';
const config = {
core: {
authorizationKey: 'localhost',
key: 'user_id'
},
features: {
'reporting_v2': 'on', // example with just a string value for the treatment
'billing_updates': { treatment: 'visa', config: '{ "color": "blue" }' }, //example of a defined config
'show_status_bar': { treatment: 'off', config: null } // example of a null config
}
};
const MyApp = () => {
return (
<SplitFactory config={config} >
<SplitTreatments names={['reporting_v2']} >
{({ isReady, treatments }) => {
return <div>{`${isReady ? 'SDK ready.' : 'SDK not ready.'} Feature flag reporting_v2 is ${treatments['reporting_v2'].treatment}`}</div>;
}}
</SplitTreatments>
</SplitFactory>
);
};
describe('MyApp', () => {
test('renders the correct treatment', (done) => {
const wrapper = mount(<MyApp/>);
// On the initial rendered output, the SDK is not ready. So treatment values are control.
expect(wrapper.html().includes('SDK not ready. Feature flag reporting_v2 is control')).toBeTruthy();
// In localhost mode, the SDK is ready and the component re-rendered with the mocked treatment on next event-loop tick.
// So we call `wrapper.update()` in a timeout callback to re-render the component.
setTimeout(() => {
wrapper.update();
expect(wrapper.html().includes('SDK ready. Feature flag reporting_v2 is on')).toBeTruthy();
done();
}, 0);
});
});
import React from 'react';
// React Test Renderer: https://reactjs.org/docs/test-renderer.html
import { create } from 'react-test-renderer';
import { SplitFactory, SplitTreatments } from '@splitsoftware/splitio-react';
const config = {
core: {
authorizationKey: 'localhost',
key: 'user_id'
},
features: {
'reporting_v2': 'on', // example with just a string value for the treatment
'billing_updates': { treatment: 'visa', config: '{ "color": "blue" }' }, //example of a defined config
'show_status_bar': { treatment: 'off', config: null } // example of a null config
}
};
const MyApp = () => {
return (
<SplitFactory config={config} >
<SplitTreatments names={['reporting_v2']} >
{({ isReady, treatments }) => {
return <div>{`${isReady ? 'SDK ready.' : 'SDK not ready.'} Feature flag reporting_v2 is ${treatments['reporting_v2'].treatment}`}</div>;
}}
</SplitTreatments>
</SplitFactory>
);
};
describe('MyApp', () => {
test('renders the correct treatment', (done) => {
const root = create(<MyApp/>);
// On the initial rendered output, the SDK is not ready. So treatment values are control.
expect(root.toJSON().children[0]).toEqual('SDK not ready. Feature flag reporting_v2 is control');
// In localhost mode, the SDK is ready and the component re-rendered with the mocked treatment on next event-loop tick.
// So we call `root.update` in a timeout callback to re-render the component.
setTimeout(() => {
root.update(<MyApp/>);
expect(root.toJSON().children[0]).toEqual('SDK ready. Feature flag reporting_v2 is on');
done();
}, 0);
});
});
Manager
Use the Split manager to get a list of features available to the Split client. To access the Manager in your code base, use the useManager
hook from the library inside the context of a SplitFactory. If you execute this outside a factory context, the method returns null.
import { useManager } from '@splitsoftware/splitio-react';
const manager = useManager();
// Now you can interact with the manager as with the regular SDK.
let flagNames = manager.names();
// If you want to make sure is ready to return Split's data from the manager object itself, you could use the .ready() promise.
manager.ready().then(() => {
// Manager is ready to be used.
flagNames = manager.names();
});
import { useManager } from '@splitsoftware/splitio-react';
const manager: SplitIO.IManager | null = useManager();
// Now you can interact with the manager as with the regular SDK.
let flagNames: SplitIO.SplitNames = manager.names();
// If you want to make sure is ready to return Split's data from the manager object itself, you could use the .ready() promise.
manager.ready().then(() => {
// Manager is ready to be used.
flagNames = manager.names();
});
To find all the details on the Manager available methods, see the JavaScript SDK Manager section.
Listener
Split SDKs send impression data back to Split servers periodically and as a result of evaluating feature flags. To additionally send this information to a location of your choice, define and attach an impression listener. For that purpose, the SDK's configurations have a parameter called impressionListener
where an implementation of ImpressionListener
could be added. This implementation must define the logImpression
method and it receives data in the following schema.
Name | Type | Description |
---|---|---|
impression | Object | |
attributes | Object | A map of attributes used on the evaluation (if any). |
sdkLanguageVersion | String | The version of the SDK. In this case the language is javascript plus the version of the underlying SDK. |
Note
There are two additional keys on this object, ip
and hostname
. They are not used on the browser.
Implement custom impression listener
The following is an example of how to implement a custom impression listener:
import { SplitFactory } from '@splitsoftware/splitio-react';
function logImpression(impressionData) {
// do something with the impression data.
}
// Create the config for the SDK factory.
const sdkConfig = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
key: 'key'
},
impressionListener: {
logImpression: logImpression
}
});
// Using the SplitFactory component.
const App = () => (
<SplitFactory config={sdkConfig} >
<MyApp />
</SplitFactory>
);
import { SplitFactory } from '@splitsoftware/splitio-react';
class MyImprListener implements SplitIO.IImpressionListener {
logImpression(impressionData: SplitIO.ImpressionData) {
// do something with impressionData
}
}
// Create the config for the SDK factory.
const sdkConfig = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
key: 'key'
},
impressionListener: {
logImpression: new MyImprListener()
}
});
// Using the SplitFactory component.
const App: React.ComponentType = () => (
<SplitFactory config={sdkConfig} >
<MyApp />
</SplitFactory>
);
An impression listener is called asynchronously from the corresponding evaluation, but is almost immediate.
Even though the SDK does not fail if there is an exception in the listener, do not block the call stack.
Logging
To enable SDK logging in the browser, see how the SDK Logging works.
This library own logger is not configurable yet, but will be very soon!
Advanced use cases
This section describes advanced use cases and features provided by the SDK.
Instantiate multiple SDK clients
Each JavaScript SDK client is tied to one specific customer id at a time which usually belongs to one traffic type (for example, user
, account
, organization
). This enhances performance and reduces data cached within the SDK.
Split supports the ability to release based on multiple traffic types. With traffic types, you can release to users
in one feature flag and accounts
in another. If you are unfamiliar with using multiple traffic types, you can learn more here.
If you need to roll out feature flags by different traffic types, instantiate multiple SDK clients, one for each traffic type. For example, you may want to roll out the feature user-poll
by users
and the feature account-permissioning
by accounts
. You can do this by generating multiple clients using the SplitClient
component or its equivalent withSplitClient
HOC, so that every descendant component is tied to the specified client object.
If you don't want to change the instance in your context but need to get a flag for a different ID, you can do it by interacting directly with the different client objects by using the useClient
hook.
See some examples below:
import { SplitFactory, SplitClient } from '@splitsoftware/splitio-react';
// Assuming this is how we setup the factory:
const sdkConfig = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
key: 'key'
}
};
const App = () => (<SplitFactory config={sdkConfig} > <MyComponentWithFlags /> </SplitFactory>);
class MyHeaderToggler extends React.Component {
// See how you can use the component to evaluate for multiple clients in your templates.
render() {
return (
<div>
{
/*
* Using SplitTreatments as a descendant of the SplitFactory with no SplitClient wrapping it,
* uses the client main client binded to the key passed in the config for evaluations.
* In this case the used key for evaluations is nicolas@split.
*/
}
<SplitTreatments names={['USER_FEATURE_FLAG_NAME']} >
{({ isReady, treatments }) => {
// Do something with the treatments for the user traffic type.
}}
</SplitTreatments>
{
/*
* To use another client, you can use the SplitClient component. Keep in mind that
* this client might not be ready yet if it's just being created and still downloading segments
* for the new key, but you can use the isReady property to block until ready.
*/
}
<SplitClient splitKey={this.props.accountId} >
<SplitTreatments names={['ACCOUNT_FEATURE_FLAG_NAME', 'ACCOUNT_FEATURE_FLAG_NAME_2']} >
{({ isReady, treatments }) => {
// Do something with the treatments for the account traffic type.
}}
</SplitTreatments>
</SplitClient>
</div>
);
}
}
import { SplitFactory, useClient } from '@splitsoftware/splitio-react';
// Assuming this is how we setup the factory:
const sdkConfig = {
core: {
authorizationKey: 'YOUR_CLIENT_SIDE_SDK_KEY',
key: 'nicolas@split'
}
};
const App = () => (
<SplitFactory config={sdkConfig} >
<MyComponentWithNewSdkClient accountId='nicolas@account' />
</SplitFactory>
);
// An example of how you can access a different SDK client to track events or evaluate flags in a function component with `useClient` hook.
function MyComponentWithNewSdkClient(props) {
// Calling useClient with no parameters returns the client on the context of this component. If MyComponentWithFlags
// is not descendant of a SplitClient component, it returns the main client associated with the key provided on initialization.
// In this case the client associated with 'nicolas@split'
const inContextClient = useClient();
// If client was already instantiated, it is just a getter like in the regular JS SDK.
const myUserClient = useClient('nicolas@split');
console.log(inContextClient === myUserClient); // logs "true"
// You can retrieve clients for as many keys as you need, although not recommended to create more than you absolutely need to.
// Keep in mind that if it is the first time you retrieve a client, it might not be ready yet.
const accountClient = useClient(this.props.accountId);
// We declares our client as an state variable, and set it once we know it is ready
const [readyAccountClient, setReadyAccountClient] = useState();
// Since SDK clients don't provide a synchronous way to check if they are ready or not, we need to listen SDK_READY event in a side-effect
useEffect(() => {
accountClient.on(accountClient.Event.SDK_READY, () => {
setReadyAccountClient(accountClient);
})
}, [accountClient])
if (readyAccountClient) {
// `accountClient` is ready to evaluate treatments as usual. To see more information about the client API go to the docs for JavaScript SDK.
const accountTreatments = readyAccountClient.getTreatments(['ACCOUNT_FEATURE_FLAG_NAME', 'ACCOUNT_FEATURE_FLAG_NAME_2']);
return (<div>{...}</div>);
} else {
// `accountClient` is not ready
return (<div>{...}</div>);
}
}
Number of SDK instances
While the SDK does not put any limitations on the number of instances that can be created, we strongly recommend keeping the number of SDKs down to one or two.
Subscribe to events and changes
The underlying JavaScript SDK has four different type of events we'll listen to:
SDK_READY
- SDK was able to download the information for the client and is ready to evaluate your flags properly.SDK_READY_FROM_CACHE
- SDK is ready to evaluate using LocalStorage cached data (which might be stale). If conditions are met, this event is emitted almost immediately since access to the cache is synchronous. Otherwise it won't fire.SDK_READY_TIMED_OUT
- When this event fires, it doesn't mean the SDK initialization is interrupted.SDK_READY
may still fire at a later time if or when the SDK finishes downloading the necessary information from the servers. This may happen with slow connections or environments which have many feature flags, segments, or dynamic configurations.SDK_UPDATE
- This event fires whenever a feature flag or segment is changed in the Split user interface.
While you could potentially access the JavaScript SDK client instances used by the React library and track this yourself, that could not be so trivial. We accept four optional boolean props for both the SplitFactory
and SplitClient
components as well as their HOC versions. If these props are set to true
, their context updates in the event of an SDK_READY
, SDK_READY_FROM_CACHE
, SDK_UPDATE
or SDK_READY_TIMED_OUT
being emitted so you can re-render or take action if you desire to do so.
- For
SDK_READY
, you can set the propupdateOnSdkReady
. Default istrue
. - For
SDK_READY_FROM_CACHE
, you can set the propupdateOnSdkReadyFromCache
. Default istrue
. - For
SDK_READY_TIMED_OUT
, you can set the propupdateOnSdkTimedout
. Default isfalse
. - For
SDK_UPDATE
, you can set the propupdateOnSdkUpdate
. Default isfalse
.
In particular, some properties at the context are updated and passed down as props to the child of SplitFactory
, SplitClient
, and SplitTreatments
, if they are defined as functions instead of React elements. These properties consist of the following status properties:
isReady
: a boolean indicating if theSDK_READY
event was triggered.isReadyFromCache
: a boolean indicating if theSDK_READY_FROM_CACHE
event was triggered.hasTimedout
: a boolean indicating if theSDK_READY_TIMED_OUT
event was triggered.isTimedout
: a boolean indicating if theSDK_READY_TIMED_OUT
event was triggered and the SDK is not ready to be consumed. Formally,isTimedout
is equivalent tohasTimeout && !isReady
.lastUpdate
: timestamp of the last listened event.
Find an example below:
function MyApp({ isReady, isReadyFromCache, isTimedout, hasTimedout, lastUpdate, factory, client }) {
/* Since this is a child of the SplitFactory, it is call again
* on SDK_READY, SDK_READY_FROM_CACHE, SDK_UPDATE and not on SDK_READY_TIMED_OUT */
return (
<div>
<SplitTreatments names={['USER_FEATURE_FLAG_NAME']} >
{
// Do something with the treatments for the user traffic type.
}
</SplitTreatments>
{ /* But we can override that setup at a per client basis, so the account one we only want to
* update on SDK_READY and SDK_READY_TIMED_OUT */ }
<SplitClient splitKey={props.accountId} updateOnSdkReadyFromCache={false} updateOnSdkUpdate={false} updateOnSdkTimedout={true} >
<SplitTreatments names={['ACCOUNT_FEATURE_FLAG_NAME', 'ACCOUNT_FEATURE_FLAG_NAME_2']} >
{({ isReady, isReadyFromCache, isTimedout, hasTimedout, lastUpdate, treatments }) => {
// Do something with the treatments for the account traffic type.
}}
</SplitTreatments>
</SplitClient>
</div>
);
};
const App = () => (
<SplitFactory config={sdkConfig} updateOnSdkUpdate={true} updateOnSdkTimedout={false} >
{/* Here MyApp function component is passed as a render prop component and not as a React element like <MyApp>.
* MyApp is called with the SplitContext object as param, which contains the SDK factory, client and status properties. */}
{MyApp}
</SplitFactory>
);
/*
* HOCs receive the flags as the 2nd, 3rd and 4th param. The signature of the returned HOC function would be:
*/
(WrappedComponent: React.ComponentType<P>, updateOnSdkUpdate?: boolean, updateOnSdkTimedout?: boolean, updateOnSdkReady?: boolean) => (props: <P>) => JSX.Element;
// So for example, if we wanted the same settings as before in the factory using the HOC
const App = withSplitFactory(sdkConfig)(MyApp, true, false);
When using the hook utils instead of the components or HOCs, you may need to access the SplitContext
directly in your components for checking the readiness state of the client in a synchronous way. Via the React useContext
function, you can access the value of the SplitContext
as shown below:
import { useContext } from 'react';
import { SplitContext } from "@splitsoftware/splitio-react";
const MyComponent = () => {
const { isReady, isTimedout } = useContext(SplitContext);
return isReady || isTimedout ?
<MyFeature /> :
<Loading />
}
import { useContext } from 'react';
import { SplitContext } from "@splitsoftware/splitio-react";
const MyComponent: React.ComponentType = () => {
const { isReady, isTimedout }: ISplitContextValues = useContext(SplitContext);
return isReady || isTimedout ?
<MyFeature /> :
<Loading />
}
The SplitContext
value object has the following structure:
interface SplitContextValue {
factory: SplitIO.ISDK,
client: SplitIO.IClient,
isReady: boolean,
isReadyFromCache: boolean,
hasTimeout: boolean,
isTimedout: boolean,
isDestroyed: boolean,
lastUpdate: number,
}
The SplitContext
exposes the internal factory and client instances of JavaScript SDK which is used underneath. While the React SDK should enable most use cases when using React, you might be in a situation where you must use the SDK functionality directly outside React component, this should give you enough flexibility. We discourage direct use of these instances unless you must.
User consent
The SDK factory allows you to disable the tracking of events and impressions until user consent is explicitly granted or declined. To learn how to configure this feature, refer to the JavaScript SDK User consent section.
Example apps
The following are example applications showing how you can integrate the React SDK into your code.
Comments
1 comment
Truly React Framework provides rich features and functionalities that helps in developing appealing and unique User Interfaces. React Framework is the crucial Development efforts put in to create attractive as well as soothing User Interface. Can you please help me with- "How different is React’s ES6 syntax when compared to ES5?"
Please sign in to leave a comment.