@bufferapp/features
TypeScript icon, indicating that this package has built-in type declarations

2.2.1 • Public • Published

@bufferapp/features

img

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
}

How to add a new attribute

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!

Readme

Keywords

none

Package Sidebar

Install

npm i @bufferapp/features

Weekly Downloads

80

Version

2.2.1

License

MIT

Unpacked Size

96.2 kB

Total Files

21

Last publish

Collaborators

  • davidluhr
  • egomezd
  • jacobchadwell
  • philippemiguet
  • josemdev
  • msanroman
  • daisymarie128
  • hamstu
  • stevenc81
  • bufferbot
  • mayauribe
  • esclapes
  • ay8s
  • mickmahady
  • dinostheo
  • hitherejoe
  • dace
  • erickhun
  • kmbriseno
  • kiriappeee
  • cmunozgar
  • peteremilbuffer
  • arekpn
  • abeeb
  • buffermw