Git Product home page Git Product logo

tdeekens / flopflip Goto Github PK

View Code? Open in Web Editor NEW
398.0 5.0 39.0 41.55 MB

🎚Flip or flop features in your React application in real-time backed by flag provider of your choice 🚦

Home Page: https://techblog.commercetools.com/embracing-real-time-feature-toggling-in-your-react-application-a5e6052716a9

License: MIT License

JavaScript 56.86% TypeScript 42.95% Shell 0.19%
redux react feature-toggles feature-flags recompose javascript launchdarkly splitio lerna rollup

flopflip's Introduction

πŸ‘‹ Hi there,

my name is Tobias Deekens. I like to design and build software for the web. I currently work at commercetools as a Principal Engineer on the Composable Commerce Team focussing on Frontend Architecture.

You can also find me on:

If you want to learn more about my work please head to my curriculum vitae. Given you're here for nonserious matters you can check out a bookshelf, listen to some music in my jukebox or enjoy some entertaining nonsense.

flopflip's People

Contributors

adnasa avatar alexhayton avatar atrakh avatar carloscortizasct avatar chrisw-b avatar danielruf avatar emmenko avatar fossabot avatar greenkeeper[bot] avatar ibratoev avatar is2ei avatar jaypea avatar jeffreyffs avatar jesse9009 avatar kerumen avatar marktran avatar montezume avatar ngbrown avatar omichelsen avatar p42-ai[bot] avatar renovate-bot avatar renovate[bot] avatar snyk-bot avatar tdeekens avatar vcapretz avatar zslabs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

flopflip's Issues

Add `withMultivariateToggle` HoC

Should add a new HoC to the react package which can be used by both react-broadcast and react-redux.

Currently multivariate toggles are supported by using injectFeatureToggles. This will pass the variate as a prop into the component. The component can then make rendering decisions based upon that. This could be eased a bit by introducing another HoC which might have the API withMultivariateToggle({<string>: <Component>}, propName:string):Component. This would inject the component as a propas specified with thepropName`.

withMultivariateToggle({
  'yellow': YellowButton,
  'red': RedButton,
  'default': Button
}, 'PrimaryButton')(Header)

this would inject a prop named PrimaryButton into the 'Header' component. Whenever none of the variations hold the default case would apply.

Named exports in @flopflip/react-broadcast ?

Describe the bug
According to the Readme (as I read it), ConfigureFlopFlip and injectFeatureToggles are available through @flipflot/react-broadcast

Basically I would like to
import { ConfigureFlopFlip } from '@flopflip/react-broadcast'; and
import { injectFeatureToggles } from '@flopflip/react-broadcast';
but it does not work.

How do I set the initial flags when using memory-adapter?

I've got memory adapter working right now with Redux. However, it looks to me like the only way to add flags to the store is by using updateFlags({ fooFeature: true });.

Is there a way where I can preconfigure all default flags? I want to do an API call to retrieve new flags and update those manually whenever I feel like they need changing, but that doesn't mean I want to block components from rendering while the store is not populated yet.

Relevant code:

Redux store

import { createStore, applyMiddleware, compose } from 'redux';
import { createFlopFlipEnhancer } from '@flopflip/react-redux';
import adapter from '@flopflip/memory-adapter';
import rootReducer from './rootReducer';

const enhancer = compose(
  ...
  createFlopFlipEnhancer(adapter),
  ...
);

export default function configureStore(initialState = {}) {
  const store = createStore(rootReducer, initialState, enhancer);
  return store;
}

Reducer setup

import { combineReducers } from 'redux';
import { flopflipReducer, FLOPFLIP_STATE_SLICE } from '@flopflip/react-redux';
import utility from './reducers/utility';

const rootReducer = combineReducers({
  [FLOPFLIP_STATE_SLICE]: flopflipReducer,
  utility,
});

export default rootReducer;

What I came up with on my own:

  1. Using createStore's preloadedState argument. This works, but it feels very wrong. This should be reserved for sending state from the server to client for state hydration. Not like a place to set default flags for my reducers.

  2. Not using flopflopReducer in my combineReducer method. I don't think this will work, and you're probably using this method for a good reason.

  3. Using updateFlags({}) on initial app load, before rendering the UI. This probably works, but it feels like a hack and like it would trigger new renders without any good reason.

TS2306: File 'typings/index.d.ts' is not a module

I've cloned the current repo and ran the build. I'm linking my current project to the cloned flopflip repo. My webpack project is throwing the following error with tslint

ERROR in /projectDir/src/components/CopyTranscript/CopyTranscript.tsx
./src/components/CopyTranscript/CopyTranscript.tsx
[tsl] ERROR in /projectDir/src/components/CopyTranscript/CopyTranscript.tsx(1,31)
      TS2306: File '/projectDir/node_modules/flopflip/packages/react-redux/dist/typings/index.d.ts' is not a module.

[@flopflip/launchdarkly-adapter] Specify different key to trigger `reconfigure`

Hey there - I'm not sure if I have a unique use case or not, but here goes:

Issue:

  • We use a user's email as their key with LaunchDarkly
  • We have some flags that we configure on a per user basis / user segments. However, we also have flags that are dependent on the custom context of what organizationId and eventId the user is currently viewing.
  • This works great when forcing a new page load when the user jumps from event to event. However, when sticking with SPA routing, I can't figure out how to force the LaunchDarkly adapter to reconfigure because technically the user's key (email) is not changing.

Solution I'm thinking of:

  • My thinking is to allow a different key to be provided to the adapter for it to check against instead of using key. For example, if I pass a prop of reconfigureKey, the adapter will use that for it's internal validation. If reconfigureKey is not provided, it falls back to key.

Example solution:

  if (adapterState.user && (adapterState.user.configureKey !== user.configureKey || adapterState.user.key !== user.key)) {
    adapterState.user = ensureUser(user);

    return changeUserContext(adapterState.user);
  }

Reference line: https://github.com/tdeekens/flopflip/blob/master/packages/launchdarkly-adapter/modules/adapter/adapter.js#L75

I wanted to make sure I'm not blind to any other provided solutions. If so, please enlighten me! πŸ€“ Otherwise, let me know if you'd be interested in this as a PR - else, I can fork.

Appreciate your hard work!

React-Redux: typescript types in selectFeatureFlag seem off

Describe the bug
ts(2345) Argument of type 'AppState' is not assignable to parameter of type 'Flags'.

To Reproduce
given i have a setup like this:

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

type AppState  = { 
  ...otherApplicationsStateSlices;
  [FLOPFLIP_STATE_SLICE]: Flags;
};

const mapStateToProps = (state: AppState) => ({
  someOtherState: state.someOtherState,
  isFeatureOn: selectFeatureFlag('fooFlagName')(state),
});

export default connect(mapStateToProps)(FooComponent);

Then i get the above mentioned typescript error.

Workaround is ugly and breaking type safety:
isFeatureOn: selectFeatureFlag('fooFlagName')((state as any) as Flags),

Expected behavior
Have another Type for the state in the definitions instead of :
export declare const selectFlag: (flagName: string) => (state: Flags) => string | boolean;
see https://github.com/tdeekens/flopflip/blob/master/packages/react-redux/modules/ducks/flags/flags.ts#L47

Is there another way to use this selector with typescript which i just didn't see?

Debounce of trottle adapter updating

Flopflip integrates with vairous adapters. Some of which maintain a websocket connection to the backing services. Anytime these servies change their state and send an event to the respective adapter the app will likely re-render in parts.

This behaviour is obviously intended and would arise with any sort of event based state updates from a remote server. However, an application gives away control over when it is re-rendered. As a result, whenever the flag provider runs into some "update-loop" of needless events being sent the UI will update recurrinly without any need.

As a result we could debounce or throttle the flag update callbacks within the adapters with a given, configured interval. This would also lead to the fact that flags updates are deferred to be only applied e.g. once every 30 seconds.

Thanks @ahmehri for reaching out.

Switch from create-react-context to React 16 context API?

I was investigating how the non-redux version works, and found you use create-react-context - should this be replaced with the new context API?

Also, for what it's worth, it was pretty confusing the non-redux package is called react-broadcast, for which there is already an npm package that has the same purpose as replacing the context api. I was looking for a while where react-broadcast was used, couldn't find it, and got more confused trying to figure out where context is coming from since there were no calls to createContext

RFC: Improved feature variate toggling through `FeatureVariate`

The idea is to simply feature variante toggling through declarative compound components using FeatureToggled and FeatureVariante.

<FeatureToggled flag="signupForm">
   <FeatureVariate variate="version-1">
       <h1>Here we go new</h1>
   </FeatureVariate>
   <FeatureVariate variate="version-2">
       <h1>Here we go old</h1>
   </FeatureVariate>
</FeatureToggled>

lerna ERR! yarn run build exited 1 in '@flopflip/launchdarkly-adapter'

This is on a fresh clone. I'm trying to just clone and build the current repo. Out of ideas.

flopflip$ npm cpx -v
6.4.1
flopflip$ npm install
npm WARN deprecated [email protected]: Package no longer supported. Contact [email protected] for more info.

> [email protected] install /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/fsevents
> node install

[fsevents] Success: "/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
Pass --update-binary to reinstall or --build-from-source to recompile

> [email protected] install /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/iltorb
> detect-libc prebuild-install || node-gyp rebuild


> [email protected] install /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup-plugin-filesize/node_modules/iltorb
> node ./scripts/install.js || node-gyp rebuild

info looking for cached prebuild @ /Users/josh/.npm/_prebuilds/9415e2-iltorb-v2.4.0-node-v57-darwin-x64.tar.gz
info found cached prebuild 
info unpacking @ /Users/josh/.npm/_prebuilds/9415e2-iltorb-v2.4.0-node-v57-darwin-x64.tar.gz
info unpack resolved to /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup-plugin-filesize/node_modules/iltorb/build/bindings/iltorb.node
info unpack required /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup-plugin-filesize/node_modules/iltorb/build/bindings/iltorb.node successfully
info install Successfully installed iltorb binary!

> [email protected] install /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/husky
> node ./bin/install.js

husky
setting up Git hooks
trying to install from sub 'node_module' directory, skipping Git hooks installation

> flopflip@ postinstall /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip
> check-node-version --package --print && yarn build

node: 8.12.0
npm: 6.4.1
yarn: 1.6.0
yarn run v1.6.0
$ NODE_ENV=production lerna run build
lerna notice cli v3.3.2
lerna info versioning independent
lerna info Executing command in 7 packages: "yarn run build"
lerna ERR! yarn run build exited 1 in '@flopflip/launchdarkly-adapter'
lerna ERR! yarn run build stdout:
$ rimraf dist/**
$ cross-env npm run build:es && npm run build:cjs

> @flopflip/[email protected] build:es /Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/packages/launchdarkly-adapter
> cross-env NODE_ENV=development rollup -c ../../rollup.config.js -f es -i modules/index.js -o dist/@flopflip-launchdarkly-adapter.es.js

info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

lerna ERR! yarn run build stderr:

modules/index.js β†’ dist/@flopflip-launchdarkly-adapter.es.js...
(!) Unresolved dependencies
https://github.com/rollup/rollup/wiki/Troubleshooting#treating-module-as-external-dependency
lodash.isequal (imported by modules/adapter/adapter.js)
lodash.camelcase (imported by modules/adapter/adapter.js)
warning (imported by modules/adapter/adapter.js)
@babel/runtime/helpers/defineProperty (imported by modules/adapter/adapter.js)
@babel/runtime/helpers/objectSpread (imported by modules/adapter/adapter.js)
@babel/runtime/helpers/slicedToArray (imported by modules/adapter/adapter.js)
@babel/runtime/helpers/typeof (imported by ../../../ldclient-js/dist/ldclient.es.js)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
modules/adapter/adapter.js: (6:9)
[!] Error: 'initialize' is not exported by ../../../ldclient-js/dist/ldclient.es.js
https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module
modules/adapter/adapter.js (6:9)
4:   FlagVariation,
5:   User,
6:   Flag,
          ^
7:   Flags,
8:   OnFlagsStateChangeCallback,
Error: 'initialize' is not exported by ../../../ldclient-js/dist/ldclient.es.js
    at error (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:3438:30)
    at Module.error (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:13342:9)
    at handleMissingExport (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:13029:21)
    at Module.traceVariable (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:13450:17)
    at ModuleScope.findVariable (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:12709:29)
    at FunctionScope.Scope.findVariable (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:4000:68)
    at Scope.findVariable (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:4000:68)
    at Identifier$$1.bind (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:9649:40)
    at CallExpression.NodeBase.bind (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:9161:23)
    at CallExpression.bind (/Library/WebServer/Documents/g2m-interactive-recording/node_modules/flopflip/node_modules/rollup/dist/rollup.js:10219:31)

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! @flopflip/[email protected] build:es: `cross-env NODE_ENV=development rollup -c ../../rollup.config.js -f es -i modules/index.js -o dist/@flopflip-launchdarkly-adapter.es.js`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the @flopflip/[email protected] build:es script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/josh/.npm/_logs/2018-09-17T19_36_25_661Z-debug.log
error Command failed with exit code 1.

lerna ERR! yarn run build exited 1 in '@flopflip/launchdarkly-adapter'
lerna WARN complete Waiting for 3 child processes to exit. CTRL-C to exit immediately.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! flopflip@ postinstall: `check-node-version --package --print && yarn build`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the flopflip@ postinstall script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/josh/.npm/_logs/2018-09-17T19_36_26_097Z-debug.log
flopflip$ 

[Idea]: Decouple from LaunchDarkly (add more adapters)

Currently flopflip is coupled to "only" LaunchDarkly as a provider for toggles this can change.

The library could potentially allow multiple providers for toggles and was actually built from the ground up to do so. Another packages/* to integrate with another provider and the configuration on of a consumer facing package should then pick any of the integrations behind the scenes. The API for an integration has a quite minimal surface area:

  1. Initialize (with allowing to defer it)
  2. Flush flags
  3. Flush updates

An integration is in charge of "doing" the targeting etc.

A simple idea is to integrate with an autoupdating/polling/streaming localstorage integration. Any app could, when receiving flags from it's backend, flush these to localstorage where the adapter/integration picks them up and they're automatically flushed to redux or the broadcasting variant.

Defaulting to camel cased flags.

I noticed multiple packages (splitio & LaunchDarkly) are converting incoming flags from their original form to be camel cased instead.

As a developer building out features using LaunchDarkly it seems to be easier to use the key supplied by the LaunchDarkly UI, copy & paste, instead of needing to camel case it before use.

I also did not see this documented anywhere. I lost a good amount of time with my flags returning false before digging into the library to see what was going on.


What's the philosophy surrounding the decision to camel case all incoming flags?

Maybe at the very least, we should add some documentation around this feature.

Launch darkly adapter doesn't support reconfiguring with an empty/undefined user

I have my app wrapped with ConfigureFlopFlip from @flopflip/react-broadcast and configured with @flopflip/launchdarkly-adapter. When I log out and pass no user property in adapterArgs, the Launch darkly adapter breaks on this line:

if (adapterState.user.key !== user.key) {

Shouldn't this be something like the following to support the logout use-case?

  if (!user || adapterState.user.key !== user.key) {
    adapterState.user = ensureUser(user);

Add `injectFeatureToggle` HoC

Injecting a single feature toggle is currently "only" supported via the `withFeatureToggle' HoC.

Whenever a component should be toggled the withFeatureToggle HoC can be used. Whenever more sophisticated decisions need to be made upon multiple flags the injectFeatureFlags HoC can also be used.

However, in some cases not a whole component needs to be toggled as per withFeatureToggle. When injecting only one toggle injectFeatureToggles can become a bit verbose as you have to specify an array and in the component have to pluck the toggle off the featureToggles-prop again.

injectFeatureToggle('isButtonOn')(Header)

Would just inject isFeatureOn into the Header as a prop based on the state of isButtonOn.

An addition would be to specify the propName for the flag which may default to isFeatureOn so the API becomes

injectFeatureToggle('isButtonOn', 'shouldShowYellowButton')(Header)

Fix Renovate config

Hi, I noticed some problems in your config and wanted to discuss them before submitting a Pull Request, as I'm not 100% sure I know how you'd like it. If you think the documentation is at fault, please feel free to point that out too.

First up, I want to alert you to renovatebot/renovate#1419
It's currently a design feature - but essentially a limitation - that config in a package.json applies only to that package.json. Early on it seemed a good way to provide different config to different package files in a monorepo, but I intend to change it. Because it's a breaking change, I was wanting to wait for feedback before merging it. In the meantime, you can move your config back to any of these files: renovate.json, .renovaterc, or .renovaterc.json so that the config applies to the entire monorepo.

If the config is placed inside a package.json once 1419 is merged then it actually needs to be inside a renovate object, not renovate-config. The latter is used for Renovate presets (that function like eslint shared configs) and not for cases like this. So in fact.. none of your config is activated right now - this repo is getting the app's default settings, which fortunately are sane.

Reviewing the content of your config:

      "extends": [
        "config:base"
      ],

This is good. Over time the bot's default settings are getting less "opinionated", as I pushed a few of those opinions into the config:base preset, which is then recommended when onboarding. For example with this preset it will automatically group dependencies that come from known monorepos like babel, react, etc.

      "packagePatterns": [
        "^@flopflip/"
      ],

This is technically invalid config, because packagePatterns needs to be inside a "packageRule", so it just ends up doing nothing. If you tell me what you were hoping to achieve, I can tell you how to do it.

      "lockFileMaintenance": {
        "enabled": true
      },

This is fine. It means every monday morning Renovate will regenerate your yarn.lock from scratch and you should see a "lock file maintenance" PR if there are any changes since the last time it was updated.

      "semanticCommits": false,

It looks like you are using semantic commits in this repo - did you definitely want to turn them off? You can configure both commit scope and type if desired.

      "prTitle": "{{semanticCommitType}}({{semanticCommitScope}}): {{#if isPin}}Pin{{else}}{{#if isRollback}}Roll back{{else}}Update{{/if}}{{/if}} {{depName}} to {{#unless isRange}}v{{/unless}}{{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}",
      "commitMessage": "{{semanticCommitType}}({{semanticCommitScope}}): Update {{depName}} to {{#unless isRange}}v{{/unless}}{{newVersion}}",

I'm not sure you need to configure/override these?

      "packageRules": [
        {
          "packagePatterns": ["*"],
          "excludePackagePatterns": [],
          "enabled": false
        }
      ]

This will in effect match every package name and therefore disable Renovate for all packages. Did you want to turn off all package updating except for your @flopflip ones or something like that? If so then the easiest thing is to add the ^@flopflip/ regex to excludePackagePatterns.

FYI I'll be back online tomorrow to follow up on any response.

`shouldDeferAdapterConfiguration` not working as intended

I have an issue with shouldDeferAdapterConfiguration in my app. I'll try to explain it: I have an XHR call which populate a user. So I set shouldDeferAdapterConfiguration={!user.key} and flopflip should not initialize until my user is ready. This is not the case. I investigated and maybe I found why.

First the component ConfigureFlopFlip mount and this condition match so the adapter is not configured. Good behaviour.

if (!this.props.shouldDeferAdapterConfiguration) {

Then, when my user is ready, componentWillReceiveProps is called with this.reconfigureOrQueue.

this.reconfigureOrQueue(nextProps.adapterArgs, { shouldOverwrite: true });

The adapter is not yet configured so setPendingAdapterArgs is called

this.adapterState === AdapterStates.CONFIGURED &&

My new user lives now in this.pendingAdapterArgs ready to be passed to the adapter but applyPendingAdapterArgs is never called

this.pendingAdapterArgs = mergeAdapterArgs(

componentDidUpdate occurs and now, this.props.shouldDeferAdapterConfiguration is false so configure is called, however this.state.appliedAdapterArgs doesn't have my user. So an error is triggered LaunchDarkly side.

I don't have enough visibility on the code yet to provide a fix. Let me know if any information can help :)

`ready` event is emitted on error when initializing client

Is your feature request related to a problem? Please describe.

A LaunchDarklyFlagFetchError is sometimes thrown during client initialization. This seems to occur because the ready event gets emitted as soon as the initialization logic has finished, regardless of whether it succeeded as explained here: launchdarkly/js-client-sdk#113 (comment)

Describe the solution you'd like

The ready event is only emitted on successful initialization

Describe alternatives you've considered

The waitUntilReady method call here

return adapterState.client.waitUntilReady().then(() => {

could be replaced with the waitForInitialization method as discussed here:
launchdarkly/js-client-sdk#113 (comment)

[RFC]: Add `ReconfigureFlopflip` component

There might be a case for a ReconfigureFlopflip component for this library.

Introduction

Flopflip is generally configured by ConfigureFloplip by passing an adapter, adapterArgs among other things. Depending on the underlying adapter the user context can change. This is handled automatically by the launchdarkly-adapter as of late. Whenever any of the user props change the underlying adapter will reconfigure through ConfigureFlopflip. This may yield a new set of flags depending on your flag configuration.

Case for the addition

A project or mine has a setup in which ConfigureFlopflip is rendered very high up the tree. Some of the user properties however as part of the adapterArgs are only known further down the component tree. This is instance can hold for any project with RR4 where a <Route> quite low in the tree. Imagine anything of that match prop should end up in the user of adapterArgs. You're stuck having to pass down an quite imperative setter function e.g. setProjectKey which somehow sideways changes the user context. If this is possible depends on the adapter. The ldAdapter exposes a changeUserContext which shallowly merges in any new passed props of a nextUser. This might seem doable at first but is not really declarative.

The same could be solved by a ReconfigureFlopflip which only receives e.g. two props nextAdapterArgs and a exact prop (defaulting to false). Depending on if exact is true the nextAdapterArgs are shallowly merged or overwritten.

Example

<Route
    exact={false}
    path="/:projectKey"
    render={routerProps => (
       <React.Fragment>
           <MyRouteComponent />
           <ReconfigureFlopflip exact={false} adapterArgs={{ projectKey: routerProps.projectKey }} />
       </React.Fragment>
    )}
/>

Changes required

  1. Adding the ReconfigureFlopflip component itself
  2. The ConfigureFlopflip has to pass the adapter (or only reconfigure) onto the context of flopflip
  3. Potentially use calculateChangeBits for the flags and reconfigure on the context to subscribe to only needed context state

React-Redux: mistaken spread on state.flags

Describe the bug
When using the @flopflip/react-redux package after the last release, when a flag is updated, only the updated flag is present in Redux state, not the merged { prevState, newFlag }. In our case, this is in combination with the launchdarkly-adapter. It's possible this was introduced with the fix for #613

To Reproduce
Configure @flopflip with launchDarkly adapter and react-redux.

  1. Given turn a flag on or off in LaunchDarkly
  2. Then the redux flag state only contains the currently-changed flag; all the others have been removed.

Expected behavior
Should have the new flag + the previous flags.

Screenshots
image

Additional context
We have tracked this down to a recent change in flopflip/packages/react-redux/modules/ducks/flags/flags.ts:

The current code is in the reducer is:

switch (action.type) {
    case UPDATE_FLAGS:
      return {
        ...state.flags,
        ...action.payload.flags,
      };

    default:
      return state;
  }

However, it's the state itself that contains the flags; since state.flags is undefined, the object spread produces just the latest flag as the new state. The correct code should be:

case UPDATE_FLAGS:
  return { ...state, ...action.payload.flags };

launchdarkly-adapter is not creating the right user

There is an issue, I guess due to the recent refactoring.
In all the adapters the configure signature is:

const configure = ({
user,
onFlagsStateChange,
onStatusStateChange,
...remainingArgs
}) => {

Whereas in the launchdarkly-adapter you spread the user arg (since this commit 4998b88):

const configure = ({
clientSideId,
onFlagsStateChange,
onStatusStateChange,
...userArgs
}) => {

This doesn't respect the API as adapterArgs should take an user key, not a spreaded key.

So there is a change that have to be made. I can PR the change but I want to open the issue first for the discussion. Should I fix the adapter and re-accept a user key in the configure signature (not spreaded) or when the FlagsSubscription component call .configure(), spread the user key?

Let me know!

TypeScript definitions not accurate

Actual behavior:

dist/index/typings/index.d.ts does not export e.g. createFlipFlopEnhancer, but @flopflip-react-redux.es.js does.

So when importing in an IDE like e.g. WebStorm, the import shows as unresolved, because it checks the definitions file.

Desired behavior:
Type definitions include all exports exposed by es.js files, so they show as resolved imports in IDEs.

Improve the `getAll` flags to get real traffic data

On the 2 feature-flags services offered by the library (launchdarkly and split.io) they have the notion of an "used" flag. A flag which received traffic during the last few days. It's great because it helps remove dead code and flag which are not used anymore.
On flopflip, we require all the flags (because the library is designed like this) so we can't have the "real" traffic data. Even if a flag is unused we won't know it.

I think we should try to find a way to improve this.

react-is dependency issue with react 16.9.0

Describe the bug
I'm not quite sure why the dependency is not being installed, but when attempting to upgrade a project to react 16.9.0, I'm getting the following:

This dependency was not found:
β €
* react-is in ./node_modules/@flopflip/react-broadcast/dist/@flopflip-react-broadcast.umd.js

When installing this manually (version is 16.9.0 for this module) things seem to work as expected. Maybe we just need to update the dep within flopflip?

UPDATE_FLAGS & UPDATE_STATUS action types are no longer exposed on ver. 5.1.0

UPDATE_FLAGS and UPDATE_STATUS action types used to be exposed on ver. 4.x.x and now they don't. I've used those two items to sync between several react applications while initialize launch darkly only once in one of those application but duplication actions values by putting it on a sort of publish/subscribers sort of solution. I would appreciate if you could expose it again instead of me creating a constant holding those values.

Update flags

Describe the bug
I am unable to update flags using UPDATE_FLAGS constant from @flopflip/react-redux when using memory-adapter and giving initial state using createFlipFlopReducer.

You can view a demo of this error here: https://codesandbox.io/s/q8n8njyyow?fontsize=14, please open Page.js file.

To Reproduce
Steps to reproduce the behavior:

  1. Given
dispatch({
      type: UPDATE_FLAGS,
      payload: {
        flags: {
          socialLogin: {
            facebook: true,
            google: true
          }
        }
      }
    });
  1. Then state is:
{
  '@flopflip': {
    flags: {
      socialLogin: {
        facebook: false,
        google: false
      },
      flags: {
        socialLogin: {
          facebook: true,
          google: true
        }
      }
    },
    status: {
      status: {
        isReady: false
      }
    }
  }
}

The reason is that the state doesn't have a flag property:
image
But the reducer expects it.

It would work if the reducer is initiated this way:

export default createFlopflipReducer({ flags: features });

since FlipFlop state will now have a flags key in the flags object.

But it will break selectFeatureFlag and similar because they expect flags to be a direct child of reducer state.

Expected behavior
I expect it to replace or merge the flags object instead of appending it.

Screenshots
Demo at: https://codesandbox.io/s/q8n8njyyow?fontsize=14

Additional context

Add option to not live update from feature flagging upstream

We have some mission critical workflows where we don't want the UI to live shift out from under our users. It would be nice to have the option to disable a live subscription to the flagging system.

I could probably hack around this with a HOC wrapper holding a <ConfigureFlopFlip /> that only passes the initial flag load down to components, but it would be nice to have this first class.

Refactor to remove `recompose` to add `react-powerplug`

The issue documents the intend to potentially refactor the library to remove recompose.

react-powerplug is a very nice library for declarative state management allowing a more natural composition than recompose.

Flopflop currently mostly uses recompose to wrap display names, inject props or render a component. Most of which could be achieved without using recompose at all. If state management would need to be abstracted into container components powerplug would shine.

Something for the next πŸš‹ride.

Allow reset of memory-adapter

First of all: Lovely package :)

I'm using the memory-adapter in an integration test to provide the default flags like this:

import memoryAdapter from '@flopflip/memory-adapter'

<ConfigureFlopFlip
  adapter={memoryAdapter}
  defaultFlags={{ someFeature: true }}
>
  <ComponentUnderTestWhichUsesFlags />
</ConfigureFlopFlip>

It would be great if it was possible to reset the memoryAdapter after each test like so

afterEach(() => {
  memoryAdapter.reset()
})

Do you think this is a good idea?

The closest thing to this I've found would be to do memoryAdapter.reconfigure(). Or should we have a dedicated test-adapter?

splitio-adapter: reconfigure does not work

Describe the bug
I was trying to configure flopflip with react-redux and splitio-adapter in the described way.

  1. use createFlopFlipEnhancer when creating the store. although there is no user-context available during store creation in my app.
  2. during app bootstrap or after login: call reconfigure on the adapter with the users key and details

the reconfigure method in https://github.com/tdeekens/flopflip/blob/master/packages/splitio-adapter/modules/adapter/adapter.ts#L192
does not change the user key and user config in the splitio client objects.

The splitio client itself does not provide an option to change the user key or user details besides configuring a new client.

this is what i do as a workaround, call configure instead of reconfigure in my redux actions after login. downside of this is, that i have to maintain some extra state to determine if the user has changed and i cannot use the createFlopFlipEnhancer

Add ability to change the user context

LaunchDarkly's js-sdk allow switching the user context which could be ported to flopflip too.

The js-sdk has the API method identify to change a users' context. An example would be to have an anonymous user on a login page but an authed within the application. This would flush initial flags down to components and later new ones based on the authed user.

Currently flopflip supports the shouldInitialize callback prop to defer initialization with LaunchDarkly on e.g. a login page. Until then the static defaultFlags would be flushed to components. Changing the users' context would allow even more flexibility towards that.

Things to watch out for

  • Empty all flags when user context changes to not mix old with new
  • Keep client on component instance in FlagsSubscription to invoke identify
  • Just watching to changing key in componentWillReceiveProps should be enough

Usage in Typescript files causes TS3205

Things were working great with the library, then a teammate translated a file into TypeScript and this error started occurring.

account_page.tsx(8,10): error TS2305: Module ''@flopflip/react-broadcast'' has no exported member 'ToggleFeature'.

I figure this is an issue with not seeing the typings associated with FlopFlip or, worse, it can not understand the directory structure of @flopflip/react|react-broadcast in its resolution.

We have moduleResolution: 'node' in our tsconfig.json.

I've attempted a few things so far, such as changing typeRoots in tsconfig.json to additionally have the paths to the /types directories in react/react-broadcast respectively, but this has not worked.

Any ideas?

Inconsistency between documentation to code

For example:
In readme.md appears:

export default branchOnFeatureToggle({
  flag: flagsNames.THE_FEATURE_TOGGLE,
  variation: 'variate1',
})(ComponentToBeToggled, ComponentToBeRenderedInstead);

instead variation must be variate according to code.

Also appears:

export default branchOnFeatureToggle({ flag: flagsNames.THE_FEATURE_TOGGLE })(
  ComponentToBeToggled,
  ComponentToBeRenderedInstead
);

while the correct code is:

export default branchOnFeatureToggle(
{ flag: flagsNames.THE_FEATURE_TOGGLE }, ComponentToBeRenderedInstead)(
  ComponentToBeToggled
);

Change the flopflip reducer key

Currently, the flopflip reducer key is defined as @flopflip. It's a problem if you want to destructure your state (in a connect() for example) because you get a parsing error due to the @.

connect(({ @flopflip, user, .. }) => ({
  ...
}))

Maybe change the key to simply flopflip would be better.

RFC: Flags Reducer for `ConfigureFlopflip`

This RFC proposes a feature to allow users to hook into and reduce state changes. This could be added to react-redux and react-broadcast.

How it would work

import { ConfigureFlopflip, FlopflipActions } from 'react-broadcast'

<ConfigureFlopflip 
  reducer={(prevState, nextState, action) => {
      if (action.type === FlopflipActions.UPDATE_FLAGS) {
        // do something e.g. evaluate if flopflip is initialised and
        // return default flags only for staff
         return nextState;
      }

      if (action.type === FlopflipActions.UPDATE_STATUS) {
         return { ...nextState, { flags: defaultFlags } };
      }
      
      return nextState;
   })
 >
   <App />
</ConfigureFlopflip>

This feature sits somewhere in between defaultFlags and targeting of flags powered by the provider. It might remove the need for defaultFlags entirely has they can be implemented in the reducer.

Use cases for the reducer prop could be:

  1. Removing the "need" for defaultFlags on ConfigureFlopflip
  2. Custom hooks into the flag "resolving" process e.g. before the adapter is initialised
  3. Logging "middleware" to log flag status changes (more valuable in a non react-redux environment)
  4. Persisting previous flags to localStorage and restoring them from there
    • Might solve the FOUC (Flash of unstyled content) when e.g. the FallbackComponent (of a branchOnFeatureFlag) renders for a split second before the adapter inititalised
    • Some adapters support this internally already

What this means for the library

Mostly that react-broadcast and react-redux would have an internalReducer which reduces state from an internal dispatch and passes that on to the reducer (which defaults to a identity function). In turn both libraries would work the same apart from react-redux handing over the result of the reducer to reduce via Redux again.

A nice side effect is that we could pass a dispatch to the render or FaaC of ConfigureFlopflip allowing consumers of the library to manipulate flags from their app without having to import the adapter.

<ConfigureFlopflip render={( { dispatch: dispatchFlopflip } ) => <App /> }>

Where further down in the tree somebody could

import { FlipFlipActions } from 'react-broadcast';

// some component stuff

dispatchFlopflip({ type: FlopflipActions.UPDATE_FLAGS, { myFeature: 'variationB' } })

This might contradict a bit the primary "source of truth" being the adapter but is an interesting thought experiment.

Note: Supporting a render-prop and FaaC on the ConfigureFlopflip might also be nice to pass down a changeUserContext whenever the adapter behind the scenes supports it.

Curious to what @dferber90 thinks. "That's silly" is a happily accepted opinion 🐱.

isAdapterReady does not mean "flag is ready"

Describe the bug
πŸ‘‹ I've run into a bit of a roadblock where isAdapterReady (or using the useAdapterStatus hook) does not necessarily imply that useFeatureToggle is actually ready at the same time from LaunchDarkly. I'm using FlopFlip to directly impact whether a page is available to load - or if a user should be redirected to the homepage. This feels like a bit of a race-condition that I'm having some trouble getting around and wanted to see if you had any thoughts. Thanks!

To Reproduce
Steps to reproduce the behavior:

const featureEnabled = useFeatureToggle('feature')

  const { isReady } = useAdapterStatus()

  if (!isReady) {
    return <div>loading</div>
  } else if (!featureEnabled) {
    return <Redirect to="/" />
  }

  return <div>page stuff</div>

Expected behavior
Show loading at first, then show "page stuff" as feature flag is verified to be available.

`onlyUpdateForKeys` is not working as expected

I didn't understand well what onlyUpdateForKeys did and it seems is not solving the case I had. Even worse, it introduces a bug.. 😞

I see 2 issues: first, you call onlyUpdateForKeys on flagNames. But the component won't have a prop named flagNames. It will have a prop propKey. So I think the comparison have to be on this.

Second, my component can have other props. So if I update these props, it won't rerender. This is very problematic.

I'm investigating on my app how to solve these issues.

Loading state not exposed to app

It would be nice if a loading prop was available either as a function as child argument, so that consumers could opt in to not rendering their apps until the connection to launch darkly has succeeded (or failed)

createFlopFlipEnhancer not working

Was trying to use createFlopFlipEnhancer in the app I'm working on and in the flopflip demo too.
The following error is showing up and am wondering why?

Unhandled promise rejection (rejection id: 79): Error: Actions must be plain objects. Use custom middleware for async actions.

Stricter support for react 16.8 ||Β 16.9

Is your feature request related to a problem? Please describe.
Using newer versions of react causes some warnings like

Warning: componentWillReceiveProps is deprecated and will be removed in the next major version. Use static getDerivedStateFromProps instead.
    
    Please update the following components: ConfigureAdapter
    
    Learn more about this warning here:
    https://fb.me/react-async-component-lifecycle-hooks

Describe the solution you'd like
Flopflip should probably drop support for react < 16.8 and rewrite the internal components to drop the deprecated lifecycle methods.

Describe alternatives you've considered
Not that I know of, maybe you have other solutions?

Additional context

commercetools/merchant-center-application-kit#801 (comment)

Rerender issues with `injectFeatureToggle`

I noticed an issue when my component has injectFeatureToggle. It injects the selected feature flags in the component as a prop. However, this is an object. So each time the "update" action is fired, the component is rerendered.
In my app, I use split.io and I needed to inject some flags at the root. It leads to perf issues because my whole tree is rerendered every 15 seconds (splitio fire an update event every 15 secs in dev, 30 in prod, even if the flags have not changed).

I think, one way to solve this would be, in the updateFlags from react-redux and react-broadcast, to deep check if the flags have changed, and if not, avoid dispatching the action. This will fix this globally (for every adapters).
Or we do this specifically in the splitio-adapter (because he's the guilty one) and we avoid calling onFlagsStateChange in the adapter.

For every cases, it would be better than dispatching an update action with an empty payload.

Launch Darkly Goals

Describe the bug
When using Launch Darkly A/B tests, I am passing through the fetchGoals to clientOptions, but I'm not sure if my click targets are actually being populated back to LD; as there service also mentions adding the following to start tracking goals:

ldClient.track("your-goal-key", user);

If this is something FlopFlip doesn't do by default, would I have access to the client and user to be able to load that?

Expected behavior
Successful clicks should talk back to LD for the goal.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.