This guide provides detailed information about our Redux SDK. This library is built on top of our regular JavaScript SDK to ease the integration in applications using Redux by providing a reducer to manage the Split-related state, a set of actions that you can use to interact with the SDK as well as some selectors to easily get Split-desired data and helper functions to access some of the underlying SDK functionality to support all use cases.
Taking advantage of our JavaScript SDK being isomorphic, we also support SSR in Node by using the underlying SDK as the Node SDK
This library also offers some extra features for users of React that are using react-redux bindings.
All of our SDKs are open source. Go to our Redux SDK GitHub repository to see the source code.
Language support and requirements
This SDK is compatible with Redux v2 and later. It requires redux-thunk package to be installed on the app, which is included by default if your project is using Redux Toolkit. This means you don't need to run npm install redux-thunk
if Redux Toolkit is already installed.
For react-redux
users, the SDK supports its v4 and later.
In SSR setups, our library code is prepared to run in Node 6+ on its CommonJS build.
Initialization
Set up Split in your code base in two steps.
1. Import the SDK into your project
The SDK is published using npm
, so it's fully integrated with your workflow. You should be able to add it with yarn
too.
npm install --save @splitsoftware/splitio-redux
yarn add @splitsoftware/splitio-redux
2. Integrate the SDK in your application
You need to combine the Split reducer with yours when creating your store and use the initSplitSdk
action creator, which returns a thunk, to set things in motion. You can use the combineReducers function of Redux on the splitio
key. You can mount it at a different key but might require some extra code if you use the specific functionality for react-redux.
For client side, following the Redux documentation recommends you should create a single store to be used as the unique source of truth for your state which is where we'll plug in the Split reducer.
For Server Side Rendering, the Redux documentation suggests creating a store per request, which is why we'll provide a function to create stores where each instance will include the Split reducer.
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { splitReducer, initSplitSdk } from '@splitsoftware/splitio-redux';
const sdkBrowserConfig = {
core: {
authorizationKey: 'YOUR_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'
}
};
// Create the Redux Store
const store = configureStore(
combineReducers({
splitio: splitReducer,
... // Combine Split reducer with your own reducers
}),
// Split SDK requires thunk middleware, which is included by default by Redux Toolkit
);
// Initialize the SDK by calling the initSplitSdk and passing the config in the parameters object.
store.dispatch(initSplitSdk({ config: sdkBrowserConfig }));
export default store;
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk'; // Requirement for asynchronous actions
import { splitReducer, initSplitSdk } from '@splitsoftware/splitio-redux';
const sdkBrowserConfig = {
core: {
authorizationKey: 'YOUR_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'
}
};
// Create the Redux Store
const store = createStore(
combineReducers({
splitio: splitReducer,
... // Combine Split reducer with your own reducers
}),
// Add thunk middleware, used by Split SDK async actions
applyMiddleware(thunk)
);
// Initialize the SDK by calling the initSplitSdk and passing the config in the parameters object.
store.dispatch(initSplitSdk({ config: sdkBrowserConfig }));
export default store;
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk'; // Requirement for asynchronous actions
import { splitReducer, initSplitSdk } from '@splitsoftware/splitio-redux';
const sdkNodeConfig = {
core: {
authorizationKey: 'YOUR_SDK_KEY'
}
};
/**
* initSplitSdk should be called only once, to keep a single Split factory instance.
* The returned action is dispatched each time a new store is created, to update
* the Split status at the state.
*/
const initSplitSdkAction = initSplitSdk({ config: sdkNodeConfig });
const reducers = combineReducers({
splitio: splitReducer,
... // Combine Split reducer with your own reducers
});
export default function storeCreator() {
// Pass the reducers combined, including the splitReducer to each new store you create.
const store = createStore(reducers, applyMiddleware(thunk));
// Dispatch the initSplitSdk returned action the new store instance.
store.dispatch(initSplitSdkAction);
return store;
}
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 API key for the environment that you are setting up the SDK for. The API key is available on your Admin Settings page, on the APIs keys tab. Select the "client side" type. This is a special type of API token with limited privileges that is used for the untrustable browsers or mobile clients. Learn more about API keys.
Using the SDK
The Split SDK via its reducer keeps up to date a portion of the store state with the following schema:
{
// 'splitio' is the key where the Split reducer is expected to be mounted.
'splitio': {
// The following properties indicate the current status of the SDK
'isReady': true, // boolean flag indicating if the SDK is ready
'isReadyFromCache': false, // boolean flag indicating if the SDK is ready from cache
'isTimedout': false, // boolean indicating if the SDK is in a timed out state. Note: it will get ready eventually unless it's misconfigured
'hasTimedout': false, // boolean indicating if the SDK has ever been in a timed out state
'isDestroyed': false, // boolean indicating if the SDK has been destroyed. Read more in the shutdown section
'lastUpdate': 56789592012, // timestamp of the last SDK state change (either timed out, got ready, destroyed or processed an update from the cloud)
/* The 'treatments' property contains the evaluations of feature flags.
* Each evaluation consist of TreatmentResult objects associated to the key used on the evaluation and the feature flag name.
* We recommend that you use the provided selector functions for ease of consumption.
*/
'treatments': {
'feature_flag_name_1': {
'key': {
'treatment': 'on',
'config': "{'copy': 'better copy', 'color': 'red'}"
}
},
'feature_flag_2': {
'key': {
'treatment': 'off',
'config': null
}
}
}
}
}
Basic use
When the SDK is initialized, it starts 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 it's 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 set the isReady
property on the Redux state based on the client that will be used. Internally we listen for the SDK_READY
event triggered by the SDK before setting isReady
as true.
Another way to know when the SDK is ready would be to provide an onReady
callback to the initSplitSdk
as part of the parameters, as also shown below.
After the SDK is ready, you can use the SDK to return the proper treatments.
import { initSplitSdk } from '@splitsoftware/splitio-redux';
function onReadyCallback() {
// Use the SDK now that it is ready to properly evaluate.
}
// Along with the config, you can provide a callback to be executed once the SDK is ready, to handle accordingly.
store.dispatch(initSplitSdk({
config: sdkConfig,
onReady: onReadyCallback
}));
// You should have already initialized the SDK.
let isSplitReady = false;
const handleChange = () => {
const isReadyFlag = store.getState().splitio.isReady;
if (isReadyFlag) {
// Keep in mind that the store subscription will be triggered any time an action is dispatched,
// and some part of the state tree may potentially have changed.
isSplitReady = true;
// Use the SDK now that it is ready to properly evaluate.
}
}
// Note: If you're using react-redux you could do this via mapStateToProps. Read more below!
store.subscribe(handleChange);
import { initSplitSdk } from '@splitsoftware/splitio-redux';
function onReadyCallback() {
// Use the SDK now that it is ready to properly evaluate.
}
// initSplitSdk action creator would return a promise. If the SDK is ready already the promise will be resolved by this time.
store.dispatch(initSplitSdk({ config: sdkConfig })).then(onReadyCallback);
The getTreatments
action creator evaluates and loads into the state the proper treatments based on the splitNames
value passed to it and the key
value. In the browser, the key value is taken from the configuration and bound to the client, so you don't need to pass it here unless you need to change the key.
Although it is recommended to wait for readiness, if the SDK is not ready when you dispatch the getTreatments
action, the library queues that evaluation and loads the result into the state once SDK_READY is emitted. If you happen to queue more than one evaluation for the same splitName
and key
we'll keep the latest set of parameters and evaluate only once.
import { getTreatments } from '@splitsoftware/splitio-redux';
// Dispatch action to evaluate and load treatments for a feature flag. The key used is the one passed in the config.
store.dispatch(getTreatments({ splitNames: ['feature_flag_1'] }));
// Or a list of feature flags.
store.dispatch(getTreatments({ splitNames: ['feature_flag_2', 'feature_flag_3'] }));
import { getTreatments } from '@splitsoftware/splitio-redux';
// Dispatch action to evaluate and load treatments for a feature flag. In Node we need to provide the key on each getTreatments.
store.dispatch(getTreatments({ splitNames: ['feature_flag_1'], key: 'key' }));
// Or a list of feature flags.
store.dispatch(getTreatments({ splitNames: ['feature_flag_2', 'feature_flag_3'], key: 'key' }));
After treatments are part of the state, use the splitio.treatments
slice of state or our selectors to access the evaluation results and write the code for the different treatments that you defined in the Split user interface. Remember to handle the client returning control in your code.
// Import treatment value selector.
import { selectTreatmentValue } from '@splitsoftware/splitio-redux');
// Get the treatment corresponding to the key bound to the client (which value is 'key' on this snippet) for feature_flag_1 feature flag.
const treatment = selectTreatmentValue(store.getState().splitio, 'feature_flag_1');
if (treatment === 'on') {
// insert on code here and use configs here as necessary
} else if (treatment === 'off') {
// insert off code here and use configs here as necessary
} else {
// insert control code here
}
// Alternatively you could access the treatments directly from the store or your own custom selectors.
const splitTreatments = store.getState().splitio.treatments;
const treatment = splitTreatments['key']['feature_flag_1'].treatment;
// Import treatment value selector.
import { selectTreatmentValue } from '@splitsoftware/splitio-redux');
// Get the treatment corresponding to the key of value 'key' for feature_flag_1 feature flag.
const treatment = selectTreatmentValue(store.getState().splitio, 'feature_flag_1', 'key');
if (treatment === 'on') {
// insert on code here and use configs here as necessary
} else if (treatment === 'off') {
// insert off code here and use configs here as necessary
} else {
// insert control code here
}
// Alternatively you can access the treatments directly from the store or your own custom selectors.
const splitTreatments = store.getState().splitio.treatments;
const treatment = splitTreatments['key']['feature_flag_1'].treatment;
It's worth mentioning that these treatments won't be updated automatically when there is a change to your feature flags or segments to avoid flickering. If you want to react to SDK events, read more here.
Attribute syntax
To target based on custom attributes, the SDK's getTreatments
action creator 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 getTreatments
action creator 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.
import { selectTreatmentValue, getTreatments } from '@splitsoftware/splitio-redux');
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"]
};
// You can pass the attributes with any getTretments action using the `attributes` key of the parameters.
store.dispatch(getTreatments({ splitNames: ['feature_flag_1'], attributes: attributes }));
const treatment = selectTreatmentValue(store.getState().splitio, 'feature_flag_1');
if (treatment === 'on') {
// insert on code here
} else if (treatment === 'off') {
// insert off code here
} else {
// insert control code here
}
import { selectTreatmentValue, getTreatments } from '@splitsoftware/splitio-redux');
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"]
};
// You can pass the attributes with any getTretments action using the `attributes` key of the parameters.
store.dispatch(getTreatments({ splitNames: ['feature_flag_1'], key: 'key', attributes: attributes }));
const treatment = selectTreatmentValue(store.getState().splitio, 'feature_flag_1', 'key');
if (treatment === 'on') {
// insert on code here
} else if (treatment === 'off') {
// insert off code here
} else {
// insert control code here
}
Multiple evaluations at once
If you want to evaluate treatments for multiple feature flags at once, you can pass a list of feature flag names to the getTreatments
action creator.
You can also evaluate multiple feature flags at once using flag sets. In that case, you can use the flagSets
property instead of the splitNames
property when calling the getTreatments
action creator. Like splitNames
, the flagSets
property must be an array of string, each one corresponding to a different flag set name.
store.dispatch(getTreatments({ splitNames: ['FEATURE_FLAG_NAME_1', 'FEATURE_FLAG_NAME_2'] }));
store.dispatch(getTreatments({ flagSets: ['frontend', 'client_side'] }));
For retrieving the treatments from the store, you can use the selectTreatmentValue
selector. Note that this selector retrieves a single treatment value for a given feature flag name, so you need to call it for each feature flag name.
// Getting treatments from the store
const treatments = {
FEATURE_FLAG_NAME_1: selectTreatmentValue(store.getState().splitio, 'FEATURE_FLAG_NAME_1'),
FEATURE_FLAG_NAME_2: selectTreatmentValue(store.getState().splitio, 'FEATURE_FLAG_NAME_2')
};
Get Treatments with Configurations
To leverage dynamic configurations with your treatments, you don't need to call a specific action creator for your evaluations. Instead, our SDK stores both the treatment and the associated config (or null if there isn't one) in the Redux state. To access this values you can either use the selectTreatmentWithConfig
selector (recommended) or just access the config from the state.
Each evaluation entry loaded into the state under the treatments
key will have the structure below:
type TreatmentWithConfig = {
treatment: string,
config: string | null
};
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 will return null
for the config parameter.
The selectTreatmentWithConfig
selector takes the exact same set of arguments as the standard selectTreatmentValue
. See below for examples on proper usage:
// Import treatment with config selector.
import { selectTreatmentWithConfig } from '@splitsoftware/splitio-redux');
// Get the TreatmentResult corresponding to the key bound to the client (which value is 'key' on this snippet) for 'feature_flag_1' feature flag.
const treatmentResult = selectTreatmentWithConfig(store.getState().splitio, 'feature_flag_1');
const config = JSON.parse(treatmentResult.config);
const treatment = treatmentResult.treatment;
if (treatment === 'on') {
// insert on code here and use configs here as necessary
} else if (treatment === 'off') {
// insert off code here and use configs here as necessary
} else {
// insert control code here
}
// Alternatively you could access the TreatmentResults directly from the store or your own custom selectors.
const splitTreatments = store.getState().splitio.treatments;
const treatmentResult = splitTreatments['key']['feature_flag_1'];
// Import treatment with config selector.
import { selectTreatmentWithConfig } from '@splitsoftware/splitio-redux');
// Get the TreatmentResult corresponding to the key of value 'key' for 'feature_flag_1' feature flag.
const treatmentResult = selectTreatmentWithConfig(store.getState().splitio, 'feature_flag_1', 'key');
const config = JSON.parse(treatmentResult.config);
const treatment = treatmentResult.treatment;
if (treatment === 'on') {
// insert on code here and use configs here as necessary
} else if (treatment === 'off') {
// insert off code here and use configs here as necessary
} else {
// insert control code here
}
// Alternatively you could access the TreatmentResults directly from the store or your own custom selectors.
const splitTreatments = store.getState().splitio.treatments;
const treatmentResult = splitTreatments['key']['feature_flag_1'];
Shutdown
Call the destroySplitSdk
function to gracefully shutdown the Split SDK by stopping all background threads, clearing caches, closing connections, and flushing the remaining unpublished impressions.
This function can be used as an action creator to update the Splitio slice of state at the store.
import { destroySplitSdk } from '@splitsoftware/splitio-redux';
// you can dispatch the action to update the status at the store
store.dispatch(destroySplitSdk());
// the dispatched Thunk action returns a promise available as the return value of dispatch itself.
// this promise always resolves once the SDK has been shutdown
store.dispatch(destroySplitSdk()).then(() => {
console.log(store.getState().splitio.isDestroyed); // prints `true`
});
import { destroySplitSdk } from '@splitsoftware/splitio-redux';
function serverClose() {
// on scenarios where you need to destroy the SDK without dispatching the action, such as on server-side,
// you can attach a callback that is invoked once the SDK has been shutdown
destroySplitSdk({
onDestroy: function() {
console.log("Split destroyed");
}
});
});
After destroySplitSdk()
is dispatched and resolved, the evaluated treatments at the store will keep their current treatment values.
But if there is any subsequent attempt to use getTreatments
action creator, the treatments will be updated to control
to keep consistency with the JS vanilla SDK. Also the manager methods (see the Manager section) will result in empty values.
Important!
Destroying the SDK is mean to be definitive. A call to the destroySplitSdk
function also destroys the factory object. Attempting to restart the destroyed SDK by using the initSplitSdk
action creator is not recommended and might lead to unexpected behavior.
Track
Use the track
method to record any actions your customers perform. Each action is known as an event
and corresponds to an event type
. Calling track
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 feature flags on your users' actions and metrics.
Learn more about using track events in feature flags.
In the examples below you can see that the track
method takes a params object with up to four arguments on the client side and five arguments on the server side. The proper data type and syntax for each are:
- key: The
key
variable used in thegetTreatments
call and firing this track event. The expected data type is String. - 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 here. Split currently supports three types of properties: strings, numbers, and booleans.
The 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
or if an incorrect input to the track
method has been provided.
In the case that a bad input has been provided, you can read more about our SDK's expected behavior here
It is important to mention that since this method does not interact with the Redux store. It's only an abstraction on top of the underlying SDK track method so you can only import one Split package.
import { track } from '@splitsoftware/splitio-redux';
const eventProperties = {package : "premium", admin : true, discount : 50};
// If you have ONLY passed the key to the SDK, the signature of the function would be:
// (meaning that there is no trafficType being set along with the key in the core section of your SDK config)
function track: (
params: { trafficType: string, eventType: string, value?: number, properties?: SplitIO.Properties }
) => boolean;
// Example with both a value and properties
const queued = track({ trafficType: 'user', eventType: 'page_load_time', value: 83.334, properties: eventProperties });
// Example with only properties
const queued = track({ trafficType: 'user', eventType: 'page_load_time', properties: eventProperties });
// Most basic event you can track would require trafficType and eventType (just skip the value or properties params if you don't have any associated with your event)
const queued = track({ trafficType: 'user', eventType: 'page_load_time' });
// If you have ALSO passed the trafficType along with the key to the SDK config, that is the one we'll associate to the event. The signature of the function would be:
// (meaning that there actually was a trafficType being set along with the key in the core section of your SDK config)
function track: (
params: { eventType: string, value?: number, properties?: SplitIO.Properties }
) => boolean;
// Example with both a value and properties
const queued = track({ eventType: 'page_load_time', value: 83.334, properties: eventProperties });
// Example with only properties
const queued = track({ eventType: 'page_load_time', properties: eventProperties });
// Most basic event you can track would require just the eventType as the traffic type was already bound to the client (just skip the value or properties params if you don't have any associated with your event)
const queued = track({ eventType: 'page_load_time' });
import { track } from '@splitsoftware/splitio-redux';
const eventProperties = {package : "premium", admin : true, discount : 50};
// On the server side the client is not bound to any key or traffic type, so you need to provide these on the track call.
function track: (
params: { key: SplitIO.SplitKey, trafficType: string, eventType: string, value?: number, properties?: SplitIO.Properties }
) => boolean;
// Example with both a value and properties
const queued = track({ key: 'key', trafficType: 'user', eventType: 'page_load_time', value: 83.334, properties: eventProperties });
// Example with only properties
const queued = track({ key: 'key', trafficType: 'user', eventType: 'page_load_time', properties: eventProperties });
// Most basic event you can track would require key, trafficType and eventType (just skip the value or properties params if you don't have any associated with your event)
const queued = track({ key: 'key', trafficType: 'user', eventType: 'page_load_time' });
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 initSplitSdk
action creator as shown in the Initialization section of this doc.
To learn about all 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 or offline 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 feature flags you want to use in the features
object map. All feature flag evaluations with getTreatments
actions 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 flag 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 feature flags treatments. | 15 |
features | A fixed mapping of which treatment to show for our mocked feature flags. | {} By default we have no mocked feature flags. |
To use the SDK in localhost mode, replace the SDK Key with "localhost", as shown in the following test example. 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.
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
}
};
store.dispatch(initSplitSdk({ config: sdkConfig }));
For a complete unit test example using Jest and React Testing Library, check App.test.js.
Manager
Use the Split Manager to get a list of feature flags available to the Split client. The Manager also uses the Split data downloaded from the cloud, so you should initialize the SDK and wait for it to be ready (as explained in the basic use section) before accessing the data through the manager. Otherwise you'll get nulls and empty arrays from it.
You can access the manager functionality through our exposed helper functions explained below: getSplitNames
, getSplit
and getSplits
.
import { getSplitNames, getSplit, getSplits } from '@splitsoftware/splitio-redux'
/**
* Returns the names of feature flags registered with the SDK.
*
* @return a List of Strings of the feature flag names.
*/
const splitNamesList: SplitIO.SplitNames = getSplitNames();
/**
* Returns the feature flag registered with the SDK of this name.
*
* @return SplitView or null.
*/
const splitView: SplitIO.SplitView = getSplit(splitName: string);
/**
* Retrieves the feature flags that are currently registered with the SDK.
*
* @return List of SplitViews.
*/
const splitViewsList: SplitIO.SplitViews = getSplits();
/**
* where each SplitView object has the following shape
*/
type SplitView = {
name: string,
trafficType: string,
killed: boolean,
treatments: Array<string>,
changeNumber: number,
configs: {
[treatmentName: string]: string
},
defaultTreatment: string,
sets: Array<string>
}
Example usage:
import { getSplitNames, initSplitSdk, getTreatments } from '@splitsoftware/splitio-redux';
// You need to initialize the SDK and wait for readiness to properly use the manager methods.
store.dispatch(initSplitSdk({ config: sdkBrowserConfig, onReady: onReadyCallback }));
function onReadyCallback() {
const myFeatureFlags = getSplitNames();
store.dispatch(getTreatments({ splitNames: myFeatureFlags }));
}
import { getSplitNames, initSplitSdk, getTreatments } from '@splitsoftware/splitio-redux';
// You need to initialize the SDK and wait for readiness to properly use the manager methods.
const initSplitSdkAction = initSplitSdk({ config: sdkNodeConfig, onReady: onReadyCallback });
let myFeatureNames = [];
function onReadyCallback() {
myFeatureNames = getSplitNames();
}
// Remember that the actions should be dispatched per request, so the results are loaded to the store that you'll return for the requesting user/entity.
// The SDK should be ready by this point so it can evaluate immediately.
function requestHandler(params) {
store.dispatch(initSplitSdkAction); // Loads Split general data into the store.
store.dispatch(getTreatments({ key: params.key, splitNames: myFeatureNames })); // Load the treatments and configs into the store.
}
To find more details on the Manager available functionality, 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
Here is an example of how to implement a custom impression listener.
import { initSplitSdk } from '@splitsoftware/splitio-redux';
function logImpression(impressionData) {
// do something with the impression data.
}
// Create the config for the SDK factory.
const sdkBrowserConfig = {
core: {
authorizationKey: 'YOUR_SDK_KEY',
key: 'key'
},
impressionListener: {
logImpression: logImpression
}
});
store.dispatch(initSplitSdk({ config: sdkBrowserConfig }));
import { initSplitSdk } from '@splitsoftware/splitio-redux';
function logImpression(impressionData) {
// do something with the impression data.
}
// Create the config for the SDK factory.
const sdkNodeConfig = {
core: {
authorizationKey: 'YOUR_SDK_KEY'
},
impressionListener: {
logImpression: logImpression
}
});
store.dispatch(initSplitSdk({ config: sdkNodeConfig }));
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 on the client side or the server side depending on where you're running the SDK.
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
When running on the client side the Redux 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, the SDK instantiates multiple clients, one for each traffic type. For example, you may want to roll out the feature flag user-poll
by users
and the feature flag account-permissioning
by accounts
.
You can do this by providing a new key to be used when triggering evaluations or tracking events. See some examples below:
import { initSplitSdk, getTreatments, track, selectTreatmentValue, selectTreatmentWithConfig } from '@splitsoftware/splitio-redux';
const sdkBrowserConfig = {
core: {
authorizationKey: 'YOUR_SDK_KEY',
key: 'CUSTOMER_ACCOUNT_ID', // This is the key that will be bound to the client.
}
};
store.dispatch(initSplitSdk({ config: sdkBrowserConfig, onReady: onReadyCallback }));
// Regular track for bound account client (where key would be CUSTOMER_ACCOUNT_ID)
const queuedAccountEvent = track({ trafficType: 'account', eventType: 'ACCOUNT_CREATED' });
// Tracking events with a key parameter on the client side will get a new client (or reuse it if already created) and track events for the given key
const queuedUserEvent = track({ key: 'CUSTOMER_USER_ID', trafficType: 'user', eventType: 'PAGELOAD', value: 7.86 });
function onReadyCallback() {
// Dispatch action to evaluate and load treatments for a feature flag (where key would be CUSTOMER_ACCOUNT_ID)
store.dispatch(getTreatments({ splitNames: ['feature_flag_1'] }));
// Providing a different key will get a new client (or reuse it if already created) and calculate treatments for this key too.
store.dispatch(getTreatments({ splitNames: ['feature_flag_2'], key: 'CUSTOMER_USER_ID' }));
// To access the values for the different clients, you can use our selectors.
// If you're using multiple keys, you should provide the key when retrieving the data with the selectors, otherwise we'll default to the first entry found.
const accountTreatment = selectTreatmentValue(store.getState().splitio, 'feature_flag_1', 'CUSTOMER_ACCOUNT_ID');
const userTreatment = selectTreatmentValue(store.getState().splitio, 'feature_flag_2', 'CUSTOMER_USER_ID');
const accountTreatmentAndConfig = selectTreatmentWithConfig(store.getState().splitio, 'feature_flag_1', 'CUSTOMER_ACCOUNT_ID');
const userTreatmentAndConfig = selectTreatmentWithConfig(store.getState().splitio, 'feature_flag_2', 'CUSTOMER_USER_ID');
}
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
The underlying JavaScript SDK has four different events:
SDK_READY_FROM_CACHE
. This event fires in client-side code if using theLOCALSTORAGE
storage type. This event fires once the SDK is ready to evaluate treatments using the rollout plan cached in localStorage from a previous session (which might be stale). If there is data in localStorage, this event fires almost immediately, since access to localStorage is fast; otherwise, it doesn't fire.SDK_READY
. This event fires once the SDK is ready to evaluate treatments using the most up-to-date version of your rollout plan, downloaded from Split servers.SDK_READY_TIMED_OUT
. This event fires if there is no cached version of your rollout plan in localStorage, and the SDK could not download the data from Split servers within the time specified by thereadyTimeout
configuration parameter. This event does not indicate that the SDK initialization was interrupted. The SDK continues downloading the rollout plan and fires theSDK_READY
event when finished. This delayedSDK_READY
event may happen with slow connections or large rollout plans with many feature flags, segments, or dynamic configurations.SDK_UPDATE
. This event fires whenever your rollout plan is changed. Listen for this event to refresh your app whenever a feature flag or segment is changed in the Split user interface.
Besides managing SDK_READY
on initialization, as explained in the basic use section, you could also add callbacks for the other events as shown below:
import { initSplitSdk } from '@splitsoftware/splitio-redux';
function onReadyCallback() {
// Use the SDK now that it is ready to properly evaluate.
}
function onReadyFromCacheCallback() {
// Use the SDK to evaluate using data from the local storage cache.
}
function onTimedoutCallback() {
// Optionally handle timeout. SDK might be ready at a later point unless there's a problem on the setup.
}
function onUpdateCallback() {
// Optionally handle SDK update event. SDK was ready and processed an update on either your feature flags or segments
// that might change the result of an evaluation.
}
// Provide the callbacks if you're using the config.
store.dispatch(initSplitSdk({
config: sdkConfig,
onReady: onReadyCallback,
onReadyFromCache: onReadyFromCacheCallback,
onTimedout: onTimedoutCallback,
onUpdate: onUpdateCallback;
}));
// If you just need to access the current status, you can get it from the splitio slice of the state
const { isReady, isReadyFromCache, isTimedout, hasTimedout, isDestroyed, lastUpdate } = store.getState().splitio;
The getTreatments
action creator accepts two optional parameters, evalOnUpdate
and evalOnReadyFromCache
, which are false
by default to avoid unwanted flickering. They are only for client side and will be ignored if set on the server side.
When evalOnUpdate
is explicitly set to true, the given treatment will be re-evaluated in the event of an SDK_UPDATE
being triggered by the underlying SDK. You can use it to re-render your components whenever there is a change due to a rollout update or a feature flag being killed.
// The results for feature_flag_1 and feature_flag_2 will be re-evaluated whenever an update is processed,
// and updated in the Redux store if they changed.
// If you wanted to stop reacting to updates, dispatch the action again with the desired key,
// feature flag names an evalOnUpdate as false (to override the behavior).
store.dispatch(getTreatments({ splitNames: ['feature_flag_1', 'feature_flag_2'], evalOnUpdate: true }));
When evalOnReadyFromCache
is explicitly set to true, the given treatment will be re-evaluated in the event of an SDK_READY_FROM_CACHE
being triggered by the underlying SDK. Therefore, this param is only relevant when using 'LOCALSTORAGE' as storage type, since otherwise the event is never emitted.
Keep in mind that if there was no cache previously loaded on the browser or the event has already fired, this parameter will take no effect. Also consider than when evaluating from cache you might be using a stale snapshot until the SDK is ready.
// The results for feature_flag_1 and feature_flag_2 will be evaluated when the Sdk is ready or an update is processed.
// However only feature_flag_1 will be evaluated also if the Sdk is ready from cache.
store.dispatch(initSplitSdk({ config: sdkBrowserConfig, onReadyFromCache: onReadyFromCacheCallback, onReady: onReadyCallback }));
store.dispatch(getTreatments({ splitNames: ['feature_flag_1'], evalOnUpdate: true, evalOnReadyFromCache: true }));
store.dispatch(getTreatments({ splitNames: ['feature_flag_2'], evalOnUpdate: true }));
function onReadyFromCacheCallback() {
// feature_flag_1 is different than 'control' since we instructed the SDK to evaluate once cache is loaded.
const feature_flag_1 = selectTreatmentValue(store.getState().splitio, 'feature_flag_1');
// feature_flag_2 is 'control' still as it wasn't evaluated with cached data.
const feature_flag_2 = selectTreatmentValue(store.getState().splitio, 'feature_flag_2');
...
}
function onReadyCallback() {
// both feature flags treatments should be different than 'control' given that any pending evaluation is calculated once SDK is ready
const feature_flag_1 = selectTreatmentValue(store.getState().splitio, 'feature_flag_1');
const feature_flag_2 = selectTreatmentValue(store.getState().splitio, 'feature_flag_2');
...
}
Usage with React + Redux
We provide extra functionality for users of react-redux with two High Order Components (HOCs). In the future we'll add more mapState functions for your convenience.
The connectSplit
High Order Component connects a given component with the splitio
slice of state and getTreatments
action creator already bound to the dispatch, so you don't need to dispatch that action yourself.
import { connectSplit, selectTreatmentValue, getSplitNames } from '@splitsoftware/splitio-redux';
class MyComponent extends React.Component {
constructor(props) {
super(props);
props.getTreatments({ splitNames: ['myFeatureFlag'] }));
};
render() {
const { splitio } = props;
const isMyFeatureFlagOn = selectTreatmentValue(splitio, 'myFeatureFlag') === 'on';
if (isMyFeatureFlagOn) {
return (<MyFeatureFlagOnComponent />);
} else {
return (<MyFeatureFlagDefaultComponent />);
}
}
}
export default connectSplit()(MyComponent);
// If you've mounted the Redux SDK reducer in a key of the state other than `splitio`, you need to provide
// a callback for retrieving the feature flag related slice of state.
// If not provided, it will default to using `state.splitio`.
export default connectSplit((state) => {
return state['my_key_for_split_reducer'];
})(MyComponent);
The connectToggler
High Order Component simplifies toggling when you have a component version for "on" treatment and a different one for any other treatments (including "control"). For example:
import { connectToggler } from '@splitsoftware/splitio-redux';
const ComponentOn = () => {
return (...);
}
const ComponentDefault = () => {
return (...);
}
// This component renders ComponentOn if 'myFeatureFlag' evaluation yielded 'on', otherwise it renders ComponentDefault
const FeatureFlagToggler = connectToggler('myFeatureFlag')(ComponentOn, ComponentDefault);
// If you need to evaluate for a different key than the one bound to the factory config,
// you can pass it as the second param of the decorator.
const key = 'key';
const FeatureFlagTogglerForOtherKey = connectToggler('myFeatureFlag', key)(ComponentOn, ComponentDefault);
// If you've mounted the Redux SDK reducer in a key of the state other than `splitio`, you need to provide
// a callback for retrieving the feature flag related slice of state as the 3rd parameter.
// If not provided, it will default to using `state.splitio`.
const FeatureFlagTogglerFromCustomStateKey = connectToggler('myFeatureFlag', key, (state) => {
return state['my_key_for_split_reducer'];
})(ComponentOn, ComponentDefault);
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.
When using the Redux SDK, you can access the underlaying SDK factory instance via the splitSdk
object, as shown below:
import { splitSdk, initSplitSdk, ... } from '@splitsoftware/splitio-redux';
// `splitSdk.factory` is null until `initSplitSdk` action creator is called
store.dispatch(initSplitSdk({ config: sdkBrowserConfig }));
splitSdk.factory.UserConsent.getStatus();
import { splitSdk, initSplitSdk, ... } from '@splitsoftware/splitio-redux';
// `splitSdk.factory` is null until `initSplitSdk` action creator is called
store.dispatch(initSplitSdk({ config: sdkBrowserConfig }));
(splitSdk.factory as SplitIO.IBrowserSDK).UserConsent.getStatus();
Example apps
Here is an example application showing how you can integrate the React SDK into your code.
Comments
1 comment
Your example of how to `configureStore` from Redux Toolkit isn't correct.
Uncaught Error: "reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers
You need a reducer key.
Also, Redux Toolkit will perform `combineReducers` under the hood - https://redux-toolkit.js.org/api/other-exports#combinereducers
So you could omit the `combineReducers`:
Please sign in to leave a comment.