flopflip
Version:
A feature toggle wrapper to use LaunchDarkly with React Redux
225 lines (162 loc) • 8.15 kB
Markdown
<p align="center">
<b style="font-size: 25px">🎛 flopflip - Feature Toggling 🎚</b><br />
<i>flip or flop a feature in LaunchDarkly with real-time updates through a redux store.</i>
</p>
<p align="center">
<img alt="Logo" src="https://raw.githubusercontent.com/tdeekens/flopflip/master/logo.png" /><br /><br />
<i>Toggle features in LaunchDarkly with their state being maintained in a redux state slice being accessible through a set of Higher-Order Components in React (via recompose).</i><br />
</p>
<details>
<summary>Want to see a demo?</summary>
<img alt="Logo" src="https://raw.githubusercontent.com/tdeekens/flopflip/master/demo.gif" />
</details>
### Status
[](https://travis-ci.org/tdeekens/flopflip) 💎 []() 💎 []()
## Installation
`yarn add flopflip` or `npm i flopflip --save`
## Demo
A minimal [demo](/demo) exists and can adjusted to point to a [custom](https://github.com/tdeekens/flopflip/blob/master/demo/src/App.js#L108) LaunchDarkly account. You would have to create feature toggles according to the existing [flags](https://github.com/tdeekens/flopflip/blob/master/demo/src/flags.js) 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](https://launchdarkly.com/) within an application written using React and Redux.
### API & exports
The `modules/index.js` 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:
```js
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';
// Maintained somewhere within your application
import user from './user';
import appReducer from './reducer';
const store = createStore(
combineReducers({
appReducer,
[FLOPFLIP_STATE_SLICE]: featureToggleReducer,
}),
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 enhance 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.
```js
import { createStore, compose, applyMiddleware } from 'redux';
import {
ConfigureFlopFlip,
flopflipReducer,
FLOPFLIP_STATE_SLICE
} from 'flopflip';
// Maintained somewhere within your application
import user from './user';
import appReducer from './reducer';
const store = createStore(
combineReducers({
appReducer,
[FLOPFLIP_STATE_SLICE]: featureToggleReducer,
}),
initialState,
compose(
applyMiddleware(...),
)
)
// Somewhere where your <App /> is rendered
<ConfigureFlopFlip user={user} clientSideId={clientSideId}>
<App />
</ConfigureFlopFlip>
```
#### `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`.
```js
import React, { Component } from 'react';
import { FeatureToggled } from 'flopflip';
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>
);
```
#### `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`:
```js
import { withFeatureToggle } from 'flopflip';
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`:
```js
import { withFeatureToggle } from 'flopflip';
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.
```js
import { injectFeatureToggles } from 'flopflip';
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` is built as a UMD module using [`rollup`](https://github.com/tdeekens/flopflip/blob/master/rollup.config.js). The distribution version is not added to `git` but created as a `preversion` [script](https://github.com/tdeekens/flopflip/blob/master/package.json).
- ...ESM just import the `dist/flopflip.es.js` within your app.
- ...it's a transpiled version accessible via the `pkg.module`
- ...CommonJS use the `dist/flopflip.umd.js`
- ...AMD use the `dist/flopflip.umd.js`
- ...`<script />` link it to `dist/flopflip.umd.js` or `dist/flopflip.umd.min.js`
All build files are part of the npm distribution using the [`files`](https://github.com/tdeekens/flopflip/blob/master/package.json) array to keep install time short.
Also feel free to use [unpkg.com](https://unpkg.com/flopflip@latest/dist/flopflip.umd.min.js) as a CDN to the [dist](https://unpkg.com/flopflip@latest/dist/) files.