/flopflip

🎚Flip or flop features in LaunchDarkly with real-time updates 🎛

Primary LanguageJavaScriptMIT LicenseMIT

🎛 flopflip - Feature Toggling 🎚
flip or flop a feature in LaunchDarkly with real-time updates through a Redux store React's context.

Logo

Toggle features in LaunchDarkly with their state being maintained in a Redux state slice or a broadcasting system (through the context) being accessible through a set of Higher-Order Components in React.

Travis CI Status Codecov Coverage Status

Want to see a demo? Logo

Package Status

Package Version Dependencies
launchdarkly-wrapper launchdarkly-wrapper Version launchdarkly-wrapper Dependencies Status
react react Version react Dependencies Status
react-broadcast react-broadcast Version react-broadcast Dependencies Status
react-redux react-redux Version react-redux Dependencies Status

Installation

This is mono repository maintained using lerna. It currently contains four packages in a launchdarkly-wrapper, react, react-redux and react-broadcast. You should not need the launchdarkly-wrapper yourself but one of our bindings (react-broadcast or react-redux). Both use the react package to share components.

Depending on the preferred integration (with or without redux)

yarn add @flopflip/react-redux or npm i @flopflip/react-redux --save

or

yarn add @flopflip/react-broadcast or npm i @flopflip/react-broadcast --save

Demo

A minimal demo exists and can adjusted to point to a custom LaunchDarkly account. You would have to create feature toggles according to the existing flags too.

Then simply run:

  1. From the repositories root: yarn build:watch
  2. From /demo: first yarn and then yarn start

A browser window should open and the network tab should show feature flags being loaded from LaunchDarkly.

Documentation

Flopflip allows you to manage feature flags through LaunchDarkly within an application written using React with or without Redux.

@flopflip/react-redux API & exports

  • createFlopFlipEnhancer a redux store enhancer to configure LaunchDarkly and add feature toggle state to your redux store
  • ConfigureFlopFlip a component to configure LaunchDarkly (alternative to the store enhancer)
  • reducer and STATE_SLICE a reducer and the state slice for the feature toggle state
  • withFeatureToggle a Higher-Order Component (HoC) to conditionally render components depending on feature toggle state
  • injectFeatureToggles a HoC to inject requested feature toggles from existing feature toggles onto the props of a component
  • FeatureToggled a component conditionally rendering its children based on the status of a passed feature flag

createFlopFlipEnhancer

Requires arguments of clientSideId:string, user:object.

  • The clientSideId is your LaunchDarkly ID.
  • The user object needs at least a key attribute. An anonymous key will be generated using uuid4 when nothing is specified. The user object can contain additional data.

reducer & STATE_SLICE

The flopflop reducer should be wired up with a combineReducers within your application in coordination with the STATE_SLICE which is used internally too to manage the location of the feature toggle states.

In context this configuration could look like:

import { createStore, compose, applyMiddleware } from 'redux';
import {
  createFlopFlipEnhancer,
  flopflipReducer,

  // We refer to this state slice in the `injectFeatureToggles`
  // HoC and currently do not support a custom state slice.
  FLOPFLIP_STATE_SLICE
} from '@flopflip/react-redux';

// Maintained somewhere within your application
import user from './user';
import appReducer from './reducer';

const store = createStore(
  combineReducers({
    appReducer,
    [FLOPFLIP_STATE_SLICE]: flopflipReducer,
  }),
  initialState,
  compose(
    applyMiddleware(...),
    createFlopFlipEnhancer(
      // NOTE:
      //   This clientId is not secret to you  and can be found
      //   within your settings on LaunchDarkly.
      window.application.env.LD_CLIENT_ID,
      user
    )
  )
)

Whenever setup is not preferred via the store enhancer the same can be achieved using the ConfigureFlopFlip component.

It takes the props:

  • The clientSideId is your LaunchDarkly ID.
  • The user object needs at least a key attribute. An anonymous key will be generated using uuid4 when nothing is specified. The user object can contain additional data.
import { createStore, compose, applyMiddleware } from 'redux';
import {
  ConfigureFlopFlip,
  flopflipReducer,
  FLOPFLIP_STATE_SLICE
} from '@flopflip/react-redux';

// Maintained somewhere within your application
import user from './user';
import appReducer from './reducer';

const store = createStore(
  combineReducers({
    appReducer,
    [FLOPFLIP_STATE_SLICE]: flopflipReducer,
  }),
  initialState,
  compose(
    applyMiddleware(...),
  )
)

// Somewhere where your <App /> is rendered

<ConfigureFlopFlip user={user} clientSideId={clientSideId}>
  <App />
</ConfigureFlopFlip>

@flopflip/react-redux API & exports

  • ConfigureFlopFlip a component to configure LaunchDarkly (using context and broadcasting over redux)
  • withFeatureToggle a Higher-Order Component (HoC) to conditionally render components depending on feature toggle state
  • injectFeatureToggles a HoC to inject requested feature toggles from existing feature toggles onto the props of a component
  • FeatureToggled a component conditionally rendering its children based on the status of a passed feature flag

The configuration is the same as mentioned above

import { ConfigureFlopFlip } from '@flopflip/react-redux';

// Somewhere where your <App /> is rendered

<ConfigureFlopFlip user={user} clientSideId={clientSideId}>
  <App />
</ConfigureFlopFlip>;

This ConfigureFlopFlip component form @flopflip/react-broadcast will use the context and a broadcasting system to reliably communicate with children toggling features.

@flopflip/react-broadcast @flopflip/react-redux API & exports for toggling

Both packages @flopflip/react-broadcast and @flopflip/react-redux export the same set of components to toggle based on features. Only the import changes depending on if you chose to integrate with redux or without. Again, behind the scenes the build on @flopflip/react to share common logic.

FeatureToggled

The component renders its children depending on the state of a given feature flag. It also allows passing an optional untoggledComponent which will be rendered whenever the feature is disabled instead of null.

import React, { Component } from 'react';
import { FeatureToggled } from '@flopflip/react-redux';
// or import { FeatureToggled } from '@flopflip/react-broadcast';
import flagsNames from './feature-flags';

export default (
  <FeatureToggled
    flag={flagsNames.THE_FEATURE_TOGGLE}
    untoggledComponent={<h3>At least there is a fallback!</h3>}
  >
    <h3>I might be gone or there!</h3>
  </FeatureToggled>
);

We actually recommend maintaining a list of constants with feature flag names somewhere within your application. This avoids typos and unexpected behavior. After all, the correct workings of your feature flags is crutial to your application.

withFeatureToggle

A HoC to conditionally render a component based on a feature toggle's state. It accepts the feature toggle name and an optional component to be rendered in case the feature is disabled.

Without a component rendered in place of the ComponentToBeToggled:

import { withFeatureToggle } from '@flopflip/react-redux';
import flagsNames from './feature-flags';

const ComponentToBeToggled = () => <h3>I might be gone or there!</h3>;

export default withFeatureToggle(flagsNames.THE_FEATURE_TOGGLE)(
  ComponentToBeToggled
);

With a component rendered in place of the ComponentToBeToggled:

import { withFeatureToggle } from '@flopflip/react-redux';
import flagsNames from './feature-flags';

const ComponentToBeToggled = () => <h3>I might be gone or there!</h3>;
const ComponentToBeRenderedInstead = () =>
  <h3>At least there is a fallback!</h3>;

export default withFeatureToggle(flagsNames.THE_FEATURE_TOGGLE)(
  ComponentToBeToggled,
  ComponentToBeRenderedInstead
);

injectFeatureToggles

This HoC matches feature toggles given against configured ones and injects the matching result. withFeatureToggle uses this to conditionally render a component.

import { injectFeatureToggles } from '@flopflip/react-redux';
import flagsNames from './feature-flags';

const Component = props => {
  if (props.featureToggles[flagsNames.TOGGLE_A])
    return <h3>Something to render!</h3>;
  else if (props.featureToggles[flagsNames.TOGGLE_B])
    return <h3>Something else to render!</h3>;

  return <h3>Something different to render!</h3>;
};

export default injectFeatureToggles([flagsNames.TOGGLE_A, flagsNames.TOGGLE_B])(
  Component
);

The feature flags will be available as props within the component allowing some custom decisions based on their value.

Module formats

@flopflip/react-redux and @flopflip/react-broadcast is built for UMD (un- and minified) and ESM using rollup.

Both our @flopflip/launchdarkly-wrapper and @flopflip/react packages are "only" build for ESM and CommonJS (not UMD) as they are meant to be consumed by a module loader to be integrated.

The package.json files contain a main and module entry to point to a CommonJS and ESM build.

  • ...ESM just import the dist/@flopflip/<package>.es.js within your app.
    • ...it's a transpiled version accessible via the pkg.module
  • ...CommonJS use the dist/@flopflip/<package>.cjsjs
  • ...AMD use the dist/@flopflip/<package>.umd.js
  • ...<script /> link it to dist/@flopflip/<package>.umd.js or dist/@flopflip/<package>.umd.min.js

All build files are part of the npm distribution using the files array to keep install time short.

Also feel free to use unpkg.com as a CDN to the dist files.