garbles / flag Goto Github PK
View Code? Open in Web Editor NEWBest-in-class interface for working with feature flags in TypeScript-based React applications
Best-in-class interface for working with feature flags in TypeScript-based React applications
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...
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.
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?
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,
);
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.
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?
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 currently use flags with redux for RN, which get persisted across app restarts. The problem is I can't:
setFlagsAction
undefined
as the valuePerhaps 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.
I guess the approach here would be to initialize the refined state object by copying all of the properties into a new record.
Importing a module fails because it can't find the compiled JS file inside the build folder: https://github.com/unbounce/flag/blob/master/package.json#L5
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.
I currently don't have this setup correctly. It fails for everything.
Can we add support to react 17? I can help with a PR, if you are okay with it.
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:
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!
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.
Copy react-router's Route
component here. https://reacttraining.com/react-router/web/api/Route
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.
As per React-Redux 6 release
There are some breaking changes that is making this package fail.
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 | });
I believe this probably has to do with the breaking changes as described in react redux.
Right now setState is called every time an action is dispatched.
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;
};
The current implementation has a few problems:
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.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
๐ค
There is one place in the code where we use React.createElement('noscript')
and it should just be null
Since React 16.3 is going to deprecate the current context API, we should rewrite and rely on https://github.com/jamiebuilds/create-react-context
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?
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, causing
combineReducers` 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...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.