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

use-ab-test

v1.3.0

Published

🧪 Simple A/B testing for React apps!

Downloads

12

Readme

badge-branches.svg badge-functions.svg badge-lines.svg badge-statements.svg

🧪 use-ab-test - Unopinionated, framework-agnostic A/B and Canary testing with React Hooks

A/B testing and Canary releases are quickly becoming more and more popular. This package attempts to simplify these processes in React by providing suitably random variant selection, simple presets for memorising selected variants for users, and easy customisation for integrating with whatever analytics software you choose to use.

The implementation is very lightweight coming in at just 2.17kb gzipped with no dependencies, and performance is strong enough to run many experiments on a single page if desired.

Installation

yarn add use-ab-test

Example Usage

Variants are configured as an array of objects with percentage properties adding up to 100.

value properties are returned from the hook, and they don't need to be serialised so you can use a component, callback or anything else you can imagine 💫

import useABtest, { ExperimentProvider, PRESETS } from 'use-ab-test';

const variants = [
    {
        value: 'Normal Message',
        percentage: 50
    },
    {
        value: 'New Awesome Message!',
        percentage: 50
    }
];

const Message = () => {
    // Message text has a 50/50 chance of being either
    // 'Normal Message' or 'New Awesome Message!'
    const messageText = useABtest('message-test', variants);

    return <div>{messageText}</div>
};

const App = () => {
    const onVariantSelect = ({ value, variantIndex, experimentId, variants }) => 
        console.log(`Selected ${value} at index ${variantIndex} for ${experimentId}`);

    return (
        // Using the `LOCAL_STORAGE` preset means this user's message variant
        // will be saved indefinitely and won't change on reload / rerender
        <ExperimentProvider preset={PRESETS.LOCAL_STORAGE} onVariantSelect={onVariantSelect}>
            <Message />
        </ExperimentProvider>
    );
};

Saving selected segments

In this example, we use a selector to get the user.id from Redux, and reference it when saving the selected value so we can analyse how this user reacted to their selected variant. This can most easily be achieved by creating a wrapper for ExperimentProvider so it can consume the value from the Redux context.

import { useSelector } from 'react-redux';
import useABtest, { ExperimentProvider, PRESETS } from 'use-ab-test';

const LoggingExperimentProvider = ({ children }) => {
    const userId = useSelector(state => state.user.id);

    const onVariantSelect = ({ value, variantIndex, experimentId }) => {
        post(`/api/${userId}/log`, {
            value, 
            variantIndex, 
            experimentId
        }).then(() => console.log('Finished saving!'));
    };

    return (
        <ExperimentProvider preset={PRESETS.LOCAL_STORAGE} onVariantSelect={onVariantSelect}>
            {children}
        </ExperimentProvider>
    );
}

const App = () => (
    <LoggingExperimentProvider>
        <Message />
    </LoggingExperimentProvider>
);

Presets

| Preset Name | Description | |-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | LOCAL_STORAGE | Store selected segments in window.localStorage. This won't expire until explicitly cleared. | | SESSION | Store selected segments in window.sessionStorage. This will expire at the end of the session (behaviour varies by browser so you should almost always prefer LOCAL_STORAGE, or a Custom Preset). |

Testing

You may come across errors when testing due to window.crypto being undefined. The package provides a set of /test-utils exports to help with this.

import { setupWindowCrypto, teardownWindowCrypto } from 'use-ab-test/test-utils';

describe('some component', () => {
    beforeEach(() => setupWindowCrypto());

    afterEach(() => teardownWindowCrypto());
});

Advanced Usage

Custom Presets

use-ab-test exports a createPreset method for easily plugging in your preferred method of storage. The keys and values you need to store are provided to the supplied callbacks.

Here's an example with universal-cookie

import { ExperimentProvider, createPreset } from "use-ab-test";
import Cookies from 'universal-cookie';

const cookies = new Cookies();

const cookieStorage = createPreset({
    set: (key, val) => cookies.set(key, val, { path: '/' }),
    get: (key) => cookies.get(key),
});

const App = ({ children }) => (
    <ExperimentProvider preset={cookieStorage}>
        {children}
    </ExperimentProvider>
);

Custom random implementation

The default random implementation uses window.crypto.getRandomValues() and is random enough for all standard usage, but providing a new implementation for random is simple! Here's an example using Math.random() instead.

const App = ({ children }) => (
    <ExperimentProvider 
        preset={PRESETS.LOCAL_STORAGE}
        random={{
            // Function to call to get random value
            handler: Math.random,
            // Lowest possible number returned from the function
            lowest: 0,
            // Highest possible number returned from the function
            highest: 1,
        }}
    >
        {children}
    </ExperimentProvider>
);

Custom keys / values

By default when using any preset, the keys and values placed in storage are handled by the package. If you want to override this behaviour, you can create a preset object manually instead of using the createPreset helper function.

const customPreset = {
    // Save the selected variant
    saveVariant: ({ value, variantIndex, experimentId, variants }) => {
        saveVariantSomehow(experimentId, variantIndex);
    },
    // Return selected variant, or `null` to make the selection again
    beforeVariantSelect: ({ value, variantIndex, experimentId, variants }) => {
        const selectedVariant = checkForSavedVariant(experimentId);

        return selectedVariant ?? null;
    },
};

const Wrapper = ({ children }) => (
    <ExperimentProvider preset={customPreset}>
        {children}
    </ExperimentProvider>
)