npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-splitz

v2.0.4

Published

A/B & Multivariate Testing library for React projects

Downloads

7

Readme

React Splitz 📊📈

Build Status npm version David GitHub

React Splitz is an A/B and multivariate testing tool for react applications.

Easy way to set and manage persistent experiments in your app and make careful changes to your user experiences while collecting data on the results. Subscribe and access experiments from any component via component props or hooks that we all know and love. It also offers full support for server-side rendering so you're able to use it in your universal web apps as well. Very performant and lightweight (~ 3.9kb minified/gzipped).

Installation

Using npm:

npm install react-splitz --save

Using yarn:

yarn add react-splitz

or UMD build (ReactSplitz as the global variable)

// Development build
<script src="https://unpkg.com/react-splitz@2/umd/react-splitz.development.js"></script>
// Production build
<script src="https://unpkg.com/react-splitz@2/umd/react-splitz.production.min.js"></script>

Setup

First we have to import our <TestContainer /> wrapper component to wrap around our app/top-level component. All configuration for experiments will be provided as props to this component.

Note: It is essential that we wrap our component around our top level application component so that experiments and chosen experiment buckets are accessible throughout the entire application.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// Import TestContainer from our package
import { TestContainer } from 'react-splitz';

/**
 * Define your experiments in an array like this. In a real
 * application it might make more sense to move this array to a config file
 * or retrieve it from an API and import it here.
 **/

const experiments = [
  {
    name: 'button-size',
    variants: [
      { name: 'default' },
      { name: 'small' },
      { name: 'medium' },
      { name: 'large' },
    ],
  },
  {
    name: 'signup-btn-color',
    variants: [
      { name: 'default' },
      { name: 'gray' },
      { name: 'blue' },
      { name: 'red' },
    ],
  },
];

ReactDOM.render(
  <TestContainer
    // Pass experiments array as a prop
    experiments={experiments}
  >
    <App />
  </TestContainer>,
  document.getElementById('root')
);

Now you can access running experiments from any component with the withTest wrapper component. In this case, we wrap the SignupButton component with it.

SignupButton.js

import React, { Component } from 'react';

// Import 'withTest' wrapper here
import { withTest } from 'react-splitz';

class SignupButton extends Component {
  render() {
    /**
     * Each of the experiment props provide a `variant` key which
     *  is the chosen variant name of the experiment.
     **/
    const { expButtonColor, expButtonSize } = this.props;
    const classes = ['signup-btn']

    if (expButtonColor.variant) { // i.e 'red'
      classes.push(`button-${expButtonColor.variant}`) // button-red
    }

    if (expButtonSize.variant) { // i.e 'medium'
      classes.push(`button-${expButtonSize.variant}`) // button-medium
    }
    <button className={classes.join(' ')}>
      {'Sign Up'}
    </button>
  }
}
// Map the experiments to prop names for your SignupButton component.
// The value of the prop will be an object with information about the chosen variant.
const mapExperimentsToProps = {
  expButtonColor: 'signup-btn-color',
  expButtonSize: 'button-size'
}

export withTest(mapExperimentsToProps)(SignupButton)

Using hooks

Instead of using the withTest Higher Order component (HOC) you can use the useExperiment hook to subscribe to an experiment. Here's an example.

// Import 'useExperiment' hook here
import { useExperiment } from 'react-splitz';

function MyComponent(props) {
  const { variant } = useExperiment('signup-btn-color');

  // Use variant here to decide how to markup your UI
}

Note: Hooks require React version 16.8 or later

Weighted Experiments

You can assign weights to experiment buckets by adding weight values in decimal values to each variant object equalling 1.

Note: Decimals values for weights have to be multiples of 0.10. (0.3, 0.2 etc)

const experiments = [
  {
    name: 'download-promo',
    variants: [
      // 30% (popup) + 20% (button) + 50% (fixed) = %100
      { name: 'popup', weight: 0.3 },
      { name: 'button', weight: 0.2 },
      { name: 'fixed', weight: 0.5 },
    ],
  },
];

User-based / Cross-device tracking

You can pass the identifier option to an experiment to ensure that the same user is always directed to the same bucket. This should be a unique identifier like a user id, user token, etc.

const experiments = [
  {
    name: 'download-promo',
    variants: [{ name: 'popup' }, { name: 'button' }, { name: 'fixed' }],
    identifier: 'lk2ds89', // User id
  },
];

Forcing Specific Variants

This option can come in handy for situations where you want to easily test a specific variant during development or quickly switch all your user traffic to a winning variant of an experiment without having to standardize the feature in your codebase right away.

All you have do is pass in the forcedExperments object prop to the <TestContainer /> component with the experiment you want to override and the variant you want to override it with.

const experiments = [
  {
    name: 'download-promo',
    variants: [
      { name: 'popup' },
      { name: 'button' },
      { name: 'fixed' }
    ]
  }
]
const forcedExperiments = {
  'download-promo': 'button' // This will force variant 'button' to be set.
}
<TestContainer
  experiments={experiments}
  forcedExperiments={forcedExperiments}
>
  <App />
</TestContainer>

Get All Running Experiments

You can pass the callback function prop getExperiments to the <TestContainer /> component which gets called once experiments are set. This function will get called with an object of currently running experiments.

Example

const getExperiments = experiments => console.log(experiments);

Disable All Experiments

If you'd like to disable all experiments at any time, pass in the disableAll prop to TestContainer and set it to true.

Server-side Rendering

Server-side rendering setup for Universal JS apps is set up the same way as a client-side only web app with the addition of 2 extra required function props. You have to provide the getCookie and setCookie function props to the <TestContainer /> component as ways to retrieve and set cookies on the server.

Make sure you also wrap your client-side application with TestContainer as well like the examples shown above.

Here's an SSR example using Express.js

import express from 'express';
import cookieParser from 'cookie-parser';
import React from 'react';

// Import TestContainer wrapper
import { TestContainer } from 'react-splitz';

const app = express();
app.use(cookieParser());

// Define experiments
const experiments = [
  {
    name: 'button-size',
    variants: [
      { name: 'default' },
      { name: 'small' },
      { name: 'medium' },
      { name: 'large' },
    ],
  },
  {
    name: 'signup-btn-color',
    variants: [
      { name: 'default' },
      { name: 'gray' },
      { name: 'blue' },
      { name: 'red' },
    ],
  },
];

app.get('/', (req, res) => {
  res.send(
    <TestContainer
      experiments={experiments}
      getCookie={x => req.cookies[x]} // Retrieve cookie on server
      setCookie={(x, y) => res.cookie(x, y)} // Set cookie on server
    >
      <App />
    </TestContainer>
  );
});

app.listen(3000);

Tracking

Using a tracking service like Mixpanel or Goole Analytics to track running experiments is very easy. You have access to running experiments either by using the getExperiments callback that fires when your application is loading or you can subscribe to individual experiments using the withTest wrapper with your components. With that you are able to tailor how and when you make calls to these tracking services based on experiments running.

Update Experiment Variants Instantly

You are able to update experiments to different variations/buckets on the fly without reloading your app with an option available through the withTest wrapper.

Use case: Building a configuration dashboard to view and manage all running experiments for local development or staging environments. You can add functionality to quickly switch between variants/buckets and have them update instantly throughout your entire application. See more on how to use this feature below in API Reference

Composability

The withTest higher-order component is fully composable with other higher-order components (HOCs) in your app.

So if you were using this with connect from Redux or withRouter from React Router, for example, you could do something like this.

const EnhancedComponent = connect(mapStateToProps)(
  withRouter(withTest(mapExpsToProps)(WrappedComponent))
);

Although this code works, it makes things a bit messy and hard to follow. Because of this we would recommend using a composition utility function to make things clearer to read. With that you can rewrite it as:

const enhance = compose(
  connect(mapStateToProps, null),
  withRouter,
  withTest(mapExpsToProps),
  ... // Other HOCs
)

const EnhancedComponent = enhance(WrappedComponent);

The compose utility function is provided by many third-party libraries including lodash (as lodash.flowRight), Redux, and Ramda.

API Reference

<TestContainer /> option props

  • experiments - (Array [required]): Running experiments. See above for examples.
  • disableAll - (Boolean [optional]): Option to isable all experiments. More info here Disable All Experiments.
  • forcedExperiments - (Object [optional]): An object with experiment key/values as experiment name/forced variant. More info here: Forcing specific variants
  • getCookie - (Function [optional]): A function prop provided to retrieve a browser cookie. Required for server-side rendering.
  • setCookie - (Function [optional]): A function prop provided to set a browser cookie. Required for server-side rendering.

withTest([mapExperimentsToProps], [options])([WrappedComponent])

  • [WrappedComponent] - (Function or Class): The wrapped component you want subscribed to experiments. Note: Observe above that this argument is passed in during the second function invocation.

  • [mapExperimentsToProps] - (Object): Experiments you want to subscribe to using key/value pairs. The object key will be the what you want to name the component prop for the experiment. The value you provide will be the experiment name. chosen variant/bucket information. More info on the value below

  • [options] - (Object [optional]): An object that you can pass in as the third argument for extra functionality. Options are below:

    • [updateExperiments] - (Boolean): Allows you to update an experiment's variant/bucket. If this option is provided and set to true, you will have access to an updateExperiments() function prop in your wrapped components that allows you to update experiments. this.props.updateExperiments(<UpdatedExperiments>, <callback>) takes in an object as the first argument with key/value pairs. The experiment name being the key and the variant being the value. Second argument is an optional callback function you can set that will fire after experiments have been updated.

    • Example

    // Updating the 'button-size' experiment with variant 'large'
    this.props.updateExperiments({
      'button-size': 'large',
    });

Experiment Prop(s) returned from [mapExperimentsToProps]

Each experiment object prop comes with the following keys.

  • variant (String) - Chosen variant name for the experiment.