use-ab-test

    1.3.0 • Public • Published

    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>
    )

    Install

    npm i use-ab-test

    DownloadsWeekly Downloads

    0

    Version

    1.3.0

    License

    MIT

    Unpacked Size

    23.2 kB

    Total Files

    9

    Last publish

    Collaborators

    • dan-js