@bufferapp/features
v2.2.1
Published
Lightweight package for reading feature flips from Split.io on client side apps.
Downloads
168
Maintainers
Keywords
Readme
@bufferapp/features
This package is a wrapper around Split.io's React SDK for use in React applications at Buffer.
If you need to access splits in a server / backend environment, please follow the instructions for setting up the Node.js SDK, as this pacakge is client-side only. If you're working in the core services, Split is already implemented there.
Requirements
This package requires your project to be using react@16
and @apollo/client
. The latter is necessary since we need to fetch the current organization ID via GraphQL in the background.
Installation
To install this package:
yarn add @bufferapp/features
OR
npm install @bufferapp/features
Setup
Wrap your app in the FeaturesWrapper
, the only requirement is that it must be a child of your ApolloProvider
. Example:
import { FeaturesWrapper } from '@bufferapp/features';
// ...
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={client}>
<FeaturesWrapper>
<App />
</FeaturesWrapper>
</ApolloProvider>
</React.StrictMode>,
document.getElementById('root')
);
Usage
Now you can request split details with the provided React hooks. (The user's current organization ID is used internally as the key
for the split, so you don't need to pass it in yourself.)
Example:
const { split, isReady } = useSplit('test-org-split');
console.log(split); // {"treatment":"off","config":"{\"foo\":\"bar\"}"}
if (!isReady) {
return <p>Loading...</p>;
}
if (split.treatment === "on") {
return <p>Hello!</p>;
} else {
return <p>Sorry, not today!</p>;
}
As shown above, the isReady
prop should be used to delay rendering, especially in cases where a split is used early in rendering and the SDK might not have fetched the split rules yet. If not used, you may see a flash of incorrect states in your UI.
The useSplitEnabled
hook is similar to the above, but returns a boolean for simple "on" / "off" splits. Example:
const { isEnabled } = useSplitEnabled('test-org-split');
console.log(isEnabled); // false
The useSplitEnabledWithConfig
hook returns the config object from a split and a boolean indicating if the split is enabled for a specific treatment. It is useful when you want to use a split to enable a feature and also pass some configuration to that feature. The config object contains the dynamic configuration for the split and it is stored in the split as a JSON string. The treatment parameter is optional and defaults to 'on'.
Additionally you can use pass attributes parameter to be used in the split definition. See next section to find out more about this.
You can read more about Split Dynamic Configuration
Some examples:
// my_split_1 has 2 treatments with configuration
// treatment on => config: {title: "Title_1"}
// treatment off => config: {title: "Title_2"}
const { config, isEnabled } = useSplitEnabledWithConfig('my_split_1');
console.log(config); // Title_1 when split is "on", Title_2 when split is "off"
console.log(isEnabled); //true when split is "on"
// my_split_2 has 3 treatments with configuration
// treatment a => config: {title: "Title_1"}
// treatment b => config: {title: "Title_2"}
// treatment c => config: {title: "Title_3"}
const { config, isEnabled } = useSplitEnabledWithConfig('my_split_2', {}, "a");
console.log(config); // Title_1 when split is "a", Title_2 when split is "b", "Title_3" when split is "c"
console.log(isEnabled); //true when split is "a"
// my_split_3 has 3 treatments with configuration
// treatment a => config: {title: "Title_1"}
// treatment b => config: {title: "Title_2"}
// treatment c => config: {title: "Title_3"}
// and use attribute signupDate to calculate the treatment
const { config, isEnabled } = useSplitEnabledWithConfig('my_split_3', {"signupDate": new Date().getTime()}, "a");
console.log(config); // Title_1 when split is "a", Title_2 when split is "b", "Title_3" when split is "c"
console.log(isEnabled); //true when split is "a"
Attributes for targeting in Split
In Split.io's dashboard Splits can be configured to target only on specific attributes.
When using the useSplit
or useSplitEnabled
hooks we automatically pass some attributes for the current organization. They are:
{
isFreePlan: Boolean // Whether the organization is on a free (unpaid) plan
isOnTrial: Boolean // Whether the organization is currently on a New Buffer Trial
isNewBuffer: Boolean // Whether the organization is currently on a New Buffer plan
location: String // Two-letter country code - e.g. UK, IN
hasNeverBeenPayingCustomer: Boolean // Whether the organization has never been a paying customer
}
More automatic attributes will be added soon.
If your split targeting logic depends on other attributes, you can add those as the second parameters of each hook. Example:
const { split } = useSplit('some-team-only-feature', { plan: 'team' });
console.log(split); // {"treatment":"on", config:null}
const { isEnabled } = useSplitEnabled('some-team-only-feature', { plan: 'team' });
console.log(isEnabled); // true
Environments
The Split SDK will decide the environment by checking the host URL of the application. When the host matches /dev.buffer.com/i
it will use the 'staging' environment in Split.io, URLs matching /local.buffer.com/i
will use 'local', and finally any other URL defaults to 'production'.
Make sure you have configured your split correctly for each of those environments, as needed.
If you'd like to force a different environment regardless of URL you can pass the environment
prop to the <FeaturesWrapper>
component. This prop can be one of 'production' | 'staging' | 'local'
.
import { FeaturesWrapper } from '@bufferapp/features';
// ...
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={client}>
{/* Even if we're running locally, we can force using the 'production' rules for splits */}
<FeaturesWrapper environment="production">
<App />
</FeaturesWrapper>
</ApolloProvider>
</React.StrictMode>,
document.getElementById('root')
);
Testing and mocking
If you'd like to force different split states for testing purposes (whether that's for manual, unit, or integration tests, etc.) you can enable the localhost
mode by passing the mockSplits
prop to your <FeaturesWrapper>
component. When using mockSplits
the environment
prop mentioned before will be ignored (since the split state is being forced locally).
import { FeaturesWrapper } from '@bufferapp/features';
// ...
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={client}>
<FeaturesWrapper mockSplits={{
"test-org-split": { treatment: "off", config: JSON.stringify({ foo: "bar" }) },
}}>
<App />
</FeaturesWrapper>
</ApolloProvider>
</React.StrictMode>,
document.getElementById('root')
);
The features defined in mockSplits
will always be returned as defined. For more details see the SDK documentation.
Tracked Events
In order for events to be tracked, our analytics (Segment) SDK / libary should be loaded and available as window.analytics
. (This should already be the case in all apps that use our Navigator).
Once that's in place, there are two events that will be automatically tracked when using this package:
1. Experiment Enrolled
In order to aid in experimentation this package will send the Segment event Experiment Enrolled
(see tracking plan) when a treatment is requested (i.e., you call useSplit
) and when the name of the split starts with eid
or geid
(case insensitive), as this is our internal designation for "Experiment ID" and "Growth Experiment ID" respectively.
2. Feature Flip Evaluated
We have stopped sending Feature Flip Evaluated
events. Read here for more context
~~Any split impression that is not tracked as an experiment (as described above) will be tracked as Feature Flip Evaluated
(see tracking plan).~~
Using the native hooks / components
The hooks provided here are meant to cover most simple cases, but since this is just a wrapper around Split.io's own React SDK, all of their hooks and components will work and in some cases will provide more functionality.
Please see Split.io's React SDK guide for further instructions.
Contributing
Pull requests and changes are welcome! Once your changes are tested and merged into main
, create a new tag with the version number you want to publish. Then go the the Releases page, click Draft a new release and select the new tag created. Once you create a release we have a fancy GitHub Action that will automatically publish your new package version to NPM!