Coder Social home page Coder Social logo

garbles / flag Goto Github PK

View Code? Open in Web Editor NEW
375.0 11.0 27.0 547 KB

Best-in-class interface for working with feature flags in TypeScript-based React applications

TypeScript 97.99% JavaScript 2.01%
feature-flags feature-toggles react suspense typescript

flag's People

Contributors

dependabot[bot] avatar emily-curry avatar garbles avatar jkillian 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  avatar  avatar  avatar  avatar  avatar  avatar

flag's Issues

Suggestion: Allow use of children instead of Component/Render

First, this is a great tool. Thank you!

Only suggestion is: it feels more intuitive to simply declare children of Flag as the default render: this means the least disruption to existing code when you flag it, because you're just wrapping some of your JSX template in a new component:

<Flag name="features.useMyCoolNewThing">
    <div>Rendered on truthy</div>
</Flag>

The remaining features/API would ideally remain the same, and anyone who prefers to use render could ideally still do so. But one would have the option of defaulting to the children, as above. One could also use fallbackRender, etc. alongside the children.

Would this be constructive? From an initial look, it seems like that would require adding the children prop to the resolve() function in flag.tsx, using the same basic approach to prioritizing the content options that are available...

TypeScript errors in .d.ts for 3.0.0-0 package

If I do a fresh npm install of flag 3.0.0-0 I receive the following errors when using TypeScript 2.6.1:

ERROR in [at-loader] ./node_modules/flag/build/flag.d.ts:13:9 
    TS1170: A computed property name in a type literal must directly refer to a built-in symbol.

ERROR in [at-loader] ./node_modules/flag/build/flag.d.ts:13:10 
    TS2304: Cannot find name 'key'.

ERROR in [at-loader] ./node_modules/flag/build/flags-provider.d.ts:12:9 
    TS1170: A computed property name in a type literal must directly refer to a built-in symbol.

ERROR in [at-loader] ./node_modules/flag/build/flags-provider.d.ts:12:10 
    TS2304: Cannot find name 'key'.

ERROR in [at-loader] ./node_modules/flag/build/flags-provider.d.ts:17:9 
    TS1170: A computed property name in a type literal must directly refer to a built-in symbol.

ERROR in [at-loader] ./node_modules/flag/build/flags-provider.d.ts:17:10 
    TS2304: Cannot find name 'key'.

I've been able to fix the problem by manually editing those definition files and changing key to key: string on the relevant lines.

Use without redux

I was wondering about how to use the library without redux. Currently using with redux and its working fine, but considering leaving redux and using alternatives. Any tips?

Could not find "store" in the context of "Connect(FlagsProvider)"

When attempting to integrate into my redux store I get the following error:

Could not find "store" in the context of "Connect(FlagsProvider)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(FlagsProvider) in connect options.

My relevant dependency versions:

My flags.ts:

import createFlags from 'flag';

export type AppFlags = {
  features: {
    queues: boolean;
    manage: boolean;
    search: boolean;
  };
  layout: {
    dragAndDrop: boolean;
  };
};

const { FlagsProvider, Flag, useFlag, useFlags } = createFlags<AppFlags>();

export { FlagsProvider, Flag, useFlag, useFlags };

import { createReduxBindings } from 'flag/redux';

const {
  setFlagsAction,
  getFlagsSelector,
  createFlagsReducer,
  ConnectedFlagsProvider,
} = createReduxBindings(FlagsProvider);

export {
  setFlagsAction,
  getFlagsSelector,
  createFlagsReducer,
  ConnectedFlagsProvider,
};

My index.tsx:

...
import { ConnectedFlagsProvider } from './flags';
...
ReactDOM.render(
  <Provider store={store}>
    <ConnectedFlagsProvider>
      <PersistGate loading={null} persistor={persistor}>
        <App history={history} />
      </PersistGate>
    </ConnectedFlagsProvider>
  </Provider>,
  document.getElementById('root') as HTMLElement,
);

Remove dependency on react-redux connect

react-redux is great, but this library should imitate what react-router-redux does and have a FlagsProvider and a ConnectedFlagsProvider (like ConnectedRouter).

The FlagsProvider can just accept an object of unresolved flags, while ConnectedFlagsProvider will wrap FlagsProvider, subscribe to the redux store and pluck state.flags off of the store and pass it into FlagsProvider.

This would also effectively nullify the awkwardness of the reducer.

Support for Immutable.js

Loving this library. Recently we decided to make the move to immutable.js and unfortunately are running into issues with integrating flag into the redux store as a plain js object. It'd be great to have an immutable variety of flag. Can we open a discussion for this?

How to use without TypeScript

Could you please explain to me, how to use this library without TypeScript? I am trying to follow README, but get an error.

My flags.js:

import createFlags from "flag";
import createReduxBindings from "flag/redux";

export const MyFlags = {
  logging: true
};

const { FlagsProvider, Flag, useFlag, useFlags } = createFlags(MyFlags);

export { FlagsProvider, Flag, useFlag, useFlags };


const {
  setFlagsAction,
  getFlagsSelector,
  createFlagsReducer,
  ConnectedFlagsProvider
} = createReduxBindings(FlagsProvider);

export {
  setFlagsAction,
  getFlagsSelector,
  createFlagsReducer,
  ConnectedFlagsProvider
};

Then, when I try to just import anything from this file as such:
import { FlagsProvider, Flag } from "./flags";
or
import { createFlagsReducer, MyFlags } from "./flags";

I get the following error:
image

Can't remove a flag

I currently use flags with redux for RN, which get persisted across app restarts. The problem is I can't:

  • replace the entire value of my flags with setFlagsAction
  • delete a single key by dispatching an action with undefined as the value

Perhaps I've used this wrong -- is there a way to either reset flags, or, remove individual keys explicitly as suggested here?

If I form an action like:

{
  payload: {
    features: {
      aircraftForm: undefined
    }
  },
  type: '@@FLAGS/SET_FLAGS'
}

I can see the action that the raw action dispatched via RN debugger is

{
  payload: {
    features: {}
  },
  type: '@@FLAGS/SET_FLAGS'
}

which doesn't achieve this.

Flag values fail to update when accessed within component without props

I was using Flags on a recent app and I noticed the Flag component fails to work when it is called within a component that takes no props. If component A makes an API call and sets flags for FlagsProvider, then (for example) a few levels of child components are used (B, which calls C, which calls D), a flag within component D will not properly update (if D has no props). So, if the API call fetches a boolean for whether a feature is enabled, Flag within D will read it as false initially, but if the API returns true, Flag within D will not update.

Adding an unrelated dummy prop to D (e.g., onChange={() => {}}) causes the Flag to work properly.

Fix CI

I currently don't have this setup correctly. It fails for everything.

Support React 17

Can we add support to react 17? I can help with a PR, if you are okay with it.

Server side error using ConnectedFlagsProvider

I'm using Nextjs for server side rendering and getting bizarre warning on server side:

Warning: setState(...): Can only update a mounting component. This usually means you called setState() outside componentWillMount() on the server. This is a no-op.

Please check the code for the ConnectedFlagsProvider component.

Here's the snippet of code I'm using with ConnectedFlagsProvider:

import App, { Container } from 'next/app';
import React from 'react';
import { Provider } from 'react-redux';
import { ConnectedFlagsProvider } from 'flag';

<Provider store={store}>
  <ConnectedFlagsProvider>
    <Component {...props} />
  </ConnectedFlagsProvider>
</Provider>

Versions of certain dependencies used in the project:

Return a function to execute

I was trying to do something like this:

      <Flag 
        name="features.myFeature" 
        render={() => this.props.history.push(routeTo('my-route', { id: personId }))} 
        fallbackRender={() => this.props.history.push(routeTo('other-route', { id: personId }))} 
      />; 

It didn't work as expected, because render and fallbackRender return a ReactElement.
Is there a way I can make something like this to work? Or maybe this could be a new feature of flag?

Thanks!

Add a flag value selector to the redux bindings

We currently use flags with redux and redux-saga. A big shortcoming we found with the setup is that while flags works great inside the redux components, we have no good way to check a flag's state from the redux middleware.

It would be very helpful to have a selector to check the value of a flag in the store - while we can brew our own, we need to duplicate all the logic used for calculated flags in the react component, and hope it will never diverge.

TS 3.5 error `Type instantiation is excessively deep and possibly infinite`

Hi,
I am using your library, version 4.2.1 with TypeScript version 3.5.3 and I get the following error:

node_modules/flag/build/create-flags.d.ts:30:5 - error TS2589: Type instantiation is excessively deep and possibly infinite.

30     useFlag<KP extends KeyPath<T>>(keyPath: KP): KeyPathValue<T, KP>;
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error.

Support for React-Redux 6

About

As per React-Redux 6 release
There are some breaking changes that is making this package fail.

Issue

I haven't taken a deep look into it but I'm getting this error when trying to run my application after upgrading to react-redux ^6.0.0

TypeError: Cannot read property 'getState' of undefined
ProxyComponent../node_modules/flag/build/connected-flags-provider.js.ConnectedFlagsProvider.componentWillMount
node_modules/flag/build/connected-flags-provider.js:29
   26 | this.store = this.context.store;
> 27 | this.setState({ flags: this.store.getState().flags });
   28 | this.unsubscribe = this.store.subscribe(function () {
   29 |     _this.setState({ flags: _this.store.getState().flags });
   30 | });

Hypothesis

I believe this probably has to do with the breaking changes as described in react redux.

App can be run at simulators, but fail when `yarn test`

image

Is it because Flag call useMemo ?

Related info.

https://reactjs.org/warnings/invalid-hook-call-warning.html

  • Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.

Thanks.

// app-test.tsx
import 'react-native';
import React from 'react';

// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';

import { WrappedApp } from '../flaggedApp';

describe('App', () => {
    let flags: any;

    beforeEach(() => {
        flags = {
            // all features: enable/disable
            showAllFeatures: true,
            // 1st features: enable/disable
            features: {
                firstFeature: true,
            },
            // string in config
            config: {
                strFlag: 'Hello world',
            },
            // number
            numFlag: 5,
            // computable
            computedFlag: (flags: any) =>
                flags.features.firstFeature
                    ? `${flags.config.strFlag}; I am the number: ${flags.numFlag}`
                    : 'Computed result is false',
        };
    });

    it('should render correctly', () => {
        const wrapper = renderer.create(<WrappedApp flags={flags} />);
        const tree = wrapper.toJSON();
        expect(tree).toMatchSnapshot();
    });
});
// flaggedApp.tsx
import { Computable } from 'deep-computed';
import React from 'react';
import App from './App';
import flags from './flag.config';
import { FlagsProvider, MyFlags } from './flags';
import ErrorBoundary from './src/components/ErrorBoundary';

export const WrappedApp = (props: { flags: Computable<MyFlags> }) => {
    const { flags } = props;

    return (
        <ErrorBoundary>
            <FlagsProvider flags={flags}>
                <App />
            </FlagsProvider>
        </ErrorBoundary>
    );
};

const flaggedApp = () => WrappedApp({ flags });

export default flaggedApp;
// flag.config.ts
import { Computable } from 'deep-computed';
import { MyFlags } from './flags';

const flags: Computable<MyFlags> = {
    // all features: enable/disable
    showAllFeatures: true,
    // 1st features: enable/disable
    features: {
        firstFeature: true,
    },
    // string in config
    config: {
        strFlag: 'Hello world',
    },
    // number
    numFlag: 5,
    // computable
    computedFlag: (flags: MyFlags) =>
        flags.features.firstFeature
            ? `${flags.config.strFlag}; I am the number: ${flags.numFlag}`
            : 'Computed result is false',
};

export default flags;
// flags.ts
import createFlags from 'flag';

const { FlagsProvider, Flag, useFlag, useFlags } = createFlags<MyFlags>();

export { FlagsProvider, Flag, useFlag, useFlags };

export type MyFlags = {
    showAllFeatures: boolean;
    features: {
        firstFeature: boolean;
    };
    config: {
        strFlag: string;
    };
    numFlag: number;
    computedFlag: string;
};

RFC: Remove computed flags and "name" prop in favor of a callback that takes full list of flags and returns a boolean

The current implementation has a few problems:

  • Computed flag functions are not really used. Their intent was to provide an easy way to create computed flag values (e.g. the boolean of some flag a AND of some flag b). In reality this is only useful if the user requires the computed flags in many places and that computed flag is toggled somehow. Otherwise, it's easy enough to compute the flag before rendering <FlagProvider> and be done with it.
  • The name prop can't be type checked. This results in trivial but easily avoidable bugs. For example, especially if someone blindly wraps a piece of code in a <Flag> and never verifies that the code renders when it's name'd flag is turned on.

Given the flags with the type {myFlag: boolean}, the following would change:

<Flag name="myFlag" component={MyNewFeature} />

to this:

<Flag value={flags => flags.myFlag} component={MyNewFeature} />

I'm not sure whether this key should be value or maybe on ๐Ÿค”

Remove DOM dependency

There is one place in the code where we use React.createElement('noscript') and it should just be null

useFlags missing in 5.0.1

Hi, most probably no bug but by intent; but useFlags hooks is no longer there in the 5.0.1 version. If by intent; any suggestions to get access to the list of all flags in a component?

Doesn't setup require adding reducer with value of STORE_KEY?

I have a confusion regarding setup for this library. The README describes configuring the redux store by setting up a store enhancer. That enhancer seems to augment both the provided reducer by adding to the collection of properties a object containing the flags with the value of STORE_KEY, i.e. __SUPER_SECRET_FEATURE_FLAGS__.

This setup doesn't work as is for me because there's no reducer with the name __SUPER_SECRET_FEATURE_FLAGS__, so when the store dispatches the '@@/INITaction the original (unrefined) reducer is called with a state object already packed with the feature flags, causingcombineReducers` to error saying:

Unexpected key "__SUPER_SECRET_FEATURE_FLAGS__" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: .... Unexpected keys will be ignored.

The feature flags aren't copied onto the store because there's nothing to handle that piece of state. I could well be confused here, but it seems as though something is missing. Any guidance?

I was only able to get this to load properly by adding a reducer like this:

const __SUPER_SECRET_FEATURE_FLAGS__ = (state = {}, action) => {
  return state;
};

But am pretty sure that will break also at some point...

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.