@splitsoftware/splitio-redux v1.11.0
Redux SDK Library
A library to easily use Splitio JS SDK with Redux and React Redux. It provides helper functions over Splitio JS SDK, so that you don't need to directly access the SDK factory and client.
INTERNAL COMMENT: several library features were adapted from the Webconsole project.
Get Started
Run examples
The /examples
folder includes two working apps with Redux and React-Redux:
- React-redux-spa is a single page application bootstrapped with Create React App.
- React-redux-ssr is an app featuring server-side rendering with Split and Redux on a node server.
$ npm install
$ npm run build
$ cd examples/react-redux-spa
/examples/react-redux-spa$ yarn install
/examples/react-redux-spa$ yarn start
...
$ cd examples/react-redux-ssr
/examples/react-redux-ssr$ npm install
/examples/react-redux-ssr$ npm run dev
Run in Webconsole REMOVE BEFORE PUBLISHING
As a PoC and for testing purposes, the library was integrated to the Admin and Login apps of the Webconsole
project, and partially integrated to the Main app.
Follow the steps below to run it:
$ npm run build
$ npm link
...
/webconsole$ git fetch
/webconsole$ git checkout sdk_redux_library_migration
/webconsole$ npm link react-sdk-library
/webconsole$ npm start
/webconsole$ npm test
Usage and API
Installation
NPM
$ npm install redux redux-thunk react-redux redux-sdk-library
Yarn
$ yarn add redux redux-thunk react-redux redux-sdk-library
Note: react-redux
is only necessary for some features of the library. Check them in section Usage with React Redux.
Create store
To use the library, we need to pass the splitReducer
to the store and the Thunk
middleware to handle async actions.
A first action must be dispatched to initialize the Splitio SDK, invoking the initSplitSdk
action creator.
Below is a simple example for creating a redux store:
// Import redux and redux-sdk-library
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunk from 'redux-thunk'
import { initSplitSdk, reducer as splitReducer } from 'redux-sdk-library'
// Instantiate the store
const storeInstance = createStore(
combineReducers({
splitio: splitReducer
/* You'll have your app reducers here too. */
}),
// The library uses async actions that require Thunk middleware
composeEnhancers(applyMiddleware(thunk))
);
// Split config, with the same format than the one provided to Split factory.
const splitBrowserConfig = {
core: {
authorizationKey: 'YOUR_BROWSER_API_KEY',
key: 'user_key'
}
};
// Dispatch action to initilize Splitio SDK
storeInstance.dispatch(initSplitSdk(splitBrowserConfig));
Note: initSplitSdk
is an async action creator that initialize the SDK. For scenarios where more than one store is created, such as server rendering, invoke initSplitSdk
once and dispatch its result on each new store:
Example:
const splitServerConfig = {
core: {
authorizationKey: 'YOUR_SDK_API_KEY'
}
};
const asyncAction = initSplitSdk(splitServerConfig);
const app = express();
app.get('*', (req, res) => {
// A new store is created per request
const store = createStore(reducers, initialState, applyMiddleware(thunk));
store.dispatch(asyncAction);
...
}
Split state
The Split reducer updates a piece of state with the following shape:
{
// 'splitio' is the key where the Split reducer is mounted
'splitio': {
// The following properties indicates the current status of the SDK
'isReady': true; // boolean indicating if the SDK has triggered an SDK_READY event
'isTimedout': false; // boolean indicating if the SDK has triggered an SDK_TIMEDOUT event
'lastUpdate': 12312312312; // timestamp of the last SDK event (SDK_READY, SDK_TIMEDOUT or SDK_UPDATED)
/* The 'treatments' property contains the evaluations of Splits.
* Each evaluation is associated with an Split name and a key (e.g., user id or organization name).
* Thus the property has 3 levels: split name, split key, and finally the treatment that was evaluated for that split and key.
*/
'treatments': { // First level: list of evaluated splits
'split_name_1': { // Second level: list of evaluated keys for the container split
'user_key': { // Third level: evaluated treatment, formed by a 'treatment' value and a config that might be null
'treatment': 'on',
'config': '{ ... }'
},
'org_key': {
'treatment': 'on',
'config': '{ ... }'
}
},
'split_name_2': {
'user_key': {
'treatment': 'treatment_A',
'config': null
}
}
}
}
}
INTERNAL COMMENTS:
- On most escenarios on browser, Splitio SDK is used with a single key. Thus a second reducer could be implemented that considers a
treatments
property without the second level. - The
treatments
property could be structured with the levelssplit key -> split name -> treatment
instead ofsplit name -> split key -> treatment
. - Treatments at the 3rd level could be normalized putting them outside the
treatments
property, in order to reduce redundancy (different keys for the same split could evaluate to the same treatment). - The possibility of multiple factories is not contemplated for the MVP.
- Treatment configs could be deserialized into JS objects before storing them. However, keeping them as strings makes it easier to compare treatments for equality inside the reducer.
Action creators
TODO: DOCUMENT ACTION CREATORS FOR SERVER-SIDE
initSplitSdk
This action creator initializes the Splitio SDK. It accepts a configuration param and returns a Thunk (async) action.
function initSplitSdk: (params: {
config: IBrowserSettings;
onReady?: () => any;
onTimedout?: () => any;
onUpdate?: () => any;
}) => ThunkAction
config
: setting object used to initialize the Split factory.
onReady
, onTimedout
and onUpdate
are optional callback functions that will be invoked on SDK_READY, SDK_READY_TIMEDOUT and SDK_UPDATE events respectively.
getTreatments
This action creator performs a treatment evaluation, i.e., it invokes the actual client.getTreatment*
methods. It accepts a param with the different objects required for an evaluation, and returns either a plain action (FSA) with the result of the evaluation, or an async action if the SDK is not ready yet.
function getTreatments: (params: {
key?: SplitKey;
splitNames: string[] | string;
attributes?: Attributes;
evalOnUpdate?: boolean;
}) => ThunkAction | FluxStandardAction
key
: optional split key. If not provided, it defaults to the key defined in the SDK setting, i.e., the config object passed to initSplitSdk
.
splitNames
: split name or array of split names to evaluate.
attributes
: optional map of attributes passed to the actual client.getTreatment*
methods.
evalOnUpdate
: indicates to re-evaluate the splits if the SDK is updated. For example, a true
value might be the desired behaviour for permission toggles or operation toggles, such as a kill switch, that you want to inmediately reflect in your app. A false
value might be useful for experiment or release toggles, where you want to keep the treatment unchanged during the sesion of the user. The param is true
by default.
Helper functions and selectors
TODO: DOCUMENT TRACK FOR SERVER-SIDE
track
This function track events, i.e., it invokes the actual client.track*
methods. It accepts a param with the different objects required for an event tracking, and returns a boolean value indicating whether or not the SDK was able to successfully queue the event to be sent back to Split servers.
function track: (params: {
key?: SplitKey;
trafficType?: string;
eventType: string;
value?: number;
properties?: Properties;
}) => boolean
key
: optional split key. If not provided, it defaults to the key defined in the SDK config object.
trafficType
: the traffic type of the key in the track call. If not provided, it defaults to the traffic type defined in the SDK config object. If not provided either in the SDK setting, the function logs an error message and returns false.
eventType
, value
and properties
follow the same rules then the client.track*
method params.
getSplitNames
Returns the names of Splits registered with the SDK. The array might be empty if the SDK was not initialized (by dispatching a "initSplitSdk" action) or if it's not ready yet.
function getSplitNames(): string[]
selectTreatmentValue
and selectTreatmentWithConfig
These functions extract a treatment evaluation from the Split state. The first one returns the treatment string value, and the second one a treatment object containing its value and configuration. See Get Treatments with Configurations for details.
function selectTreatmentValue(splitState: ISplitState, splitName: string, key?: SplitKey, defaultValue?: string): string
function selectTreatmentWithConfig(splitState: ISplitState, splitName: string, key?: SplitKey, defaultValue?: TreatmentWithConfig): TreatmentWithConfig
splitState
: Split piece of state from the store.
splitName
: split name.
key
: optional split key. If not provided, it returns the first evaluation done for splitName
.
defaultValue
: the value to return if the treatment is not found in the store (the getTreatment
evaluation was not performed for the given split and key). It defaults to 'control'
for the first selector, and { treatment: 'control', config: null}
for the second.
INTERNAL COMMENTS:
- The configs of treatments are stored as JSON strings, but we could deserialize them into JS objects.
- The
actionToggler
function in Webconsole could be also included as helper function.
Usage with React Redux
The library provides some functions to connect your React components to Split pieces of state and functionality.
These functions use the connect() method, thus you need to include react-redux
in the project.
mapTreatmentToProps
function mapTreatmentToProps(splitName: string, key?: SplitKey, getSplitState?: IGetSplitState): (state: any) => { feature: string }
This function returns a mapStateToProps
function to be used with connect
. As a result, a string property named feature
will be merged as prop for the connected component. Its value will be the treatment value evaluated for the given splitName
and key
.
getSplitState
is a optional function that takes the entire Redux state and returns the state slice which corresponds to where the Split reducer was mounted. This functionality is rarely needed, and defaults to assuming that the reducer is mounted under the splitio
key.
Example:
import { mapTreatmentToProps } from 'redux-sdk-library';
export default connect(mapTreatmentToProps('split_name'))(component);
mapIsFeatureOnToProps
function mapIsFeatureOnToProps(splitName: string, key?: SplitKey, getSplitState?: IGetSplitState): (state: any) => { isFeatureOn: string }
This function also returns a mapStateToProps
function to be used with connect
. As a result, a boolean property named isFeatureOn
will be merged as prop for the connected component. Its value will be true
if the treatment for the given splitName
and key
evaluated to 'on'
, or false
otherwise.
getSplitState
is a optional function that takes the entire Redux state and returns the state slice which corresponds to where the Split reducer was mounted. This functionality is rarely needed, and defaults to assuming that the reducer is mounted under the splitio
key.
Example:
import { mapIsFeatureOnToProps } from 'redux-sdk-library';
export default connect(mapIsFeatureOnToProps('split_name'))(component);
connectToggler
function connectToggler(splitName: string, key?: SplitKey, getSplitState?: IGetSplitState):
(ComponentOn: React.ComponentType, ComponentOff: React.ComponentType ) => React.ComponentType
The connectToggler
connector returns a HOC function given a splitName
, and an optional key
and getSplitState
function.
The returned HOC accepts two components as input and returns a wrapper component that will render ComponentOn
component if the treatment for the given split name and key is 'on'
, or ComponentOff
component otherwise.
If key
is not provided, the treatment value of the first evaluation of splitName
split is considered.
getSplitState
is a function that takes the entire Redux state and returns the state slice which corresponds to where the Split reducer was mounted. This param defaults to assuming that the reducer is mounted under the splitio
key.
Example:
import { connectToggler } from 'redux-sdk-library';
const ConnectedFeatureTogler = connectToggler(SPLIT_NAME)(FeatureComponent, LegacyComponent);
connectSplit
function connectSplit(getSplitState?: IGetSplitState):
(component: React.ComponentType) => React.ComponentType
This decorator connects your components with:
- The Split state at Redux, under the prop key
split
. See Split state for details. - The action creator
getTreatments
, binded to thedispatch
of your store. See getTreatments for details.
Example:
import { connectSplit, selectTreatmentValue } from 'redux-sdk-library';
class MyComponent extends React.Component {
componentDidMount() {
this.props.getTreatments({ splitNames: 'my_split' });
};
renderOn() { ... }
renderOtherwise() { ... }
render() {
selectTreatmentValue(this.props.split, 'my_split') === 'on' ?
renderOn() : renderOtherwise();
}
};
export default connectSplit()(myComponent);
TODO
- Define library name
- Improve build configs and project structure
- Tests
- Polish examples
- Code documentation with JSDoc format
- Review performance of reducer
- Missing feature from Webconsole:
actionToggler
License
Licensed under the Apache License, Version 2.0. See: Apache License.
About Split
Split is the leading Feature Delivery Platform for engineering teams that want to confidently deploy features as fast as they can develop them. Split’s fine-grained management, real-time monitoring, and data-driven experimentation ensure that new features will improve the customer experience without breaking or degrading performance. Companies like Twilio, Salesforce, GoDaddy and WePay trust Split to power their feature delivery.
To learn more about Split, contact hello@split.io, or get started with feature flags for free at https://www.split.io/signup.
Split has built and maintains SDKs for:
- Java Github Docs
- Javascript Github Docs
- Node Github Docs
- .NET Github Docs
- Ruby Github Docs
- PHP Github Docs
- Python Github Docs
- GO Github Docs
- Android Github Docs
- iOS Github Docs
For a comprehensive list of open source projects visit our Github page.
Learn more about Split:
Visit split.io/product for an overview of Split, or visit our documentation at help.split.io for more detailed information.
17 days ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
5 months ago
5 months ago
5 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
12 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago