Skip to content

Commit 80e947f

Browse files
authored
Support multivariate flags (#5)
* supports multi variate * add multivariate component * update readme * update flow version * fix test case name
1 parent 27f1588 commit 80e947f

11 files changed

Lines changed: 144 additions & 11 deletions

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,33 @@ This is the simplest way to render a feature flagged component when you don't ne
8888
| deprecation | boolean | false | By default if the flag evaluates to true, the child component will render. But you may want to do the reverse and enable an older version of a feature if the flag is false, which can be done by setting this prop to true |
8989
| fallback | React.Node | null | If async mode is enabled, fallback will be rendered when the flags have not yet been defined
9090

91+
### LdMultivariate
92+
93+
If your flag returns you a multivariate value, you can use this component instead of LdFeature. Which instead of rendering `React.Node` as the children, it instead expects a function that will be passed the multivariate value.
94+
95+
```js
96+
return (
97+
<LdMultivariate feature="my-feature">
98+
{(variation) => {
99+
if (variation === 'yes') return <YesComp />
100+
if (variation === 'no') return <NoComp />
101+
return <MaybeComp />
102+
}}
103+
</LdMultivariate>
104+
)
105+
```
106+
107+
children: (variation: any) => React.Node,
108+
feature: string,
109+
110+
| Props | Type | Default | Description |
111+
| ------ | ---- | ------- | ----------- |
112+
| children* | (variation: any) => React.Node | undefined | Render prop that will be passed the resulting variation that expects a React component to be returned |
113+
| feature* | string | undefined | The name of the feature flag |
114+
91115
### useLdFlag
92116

93-
Alternatively to `LdFeature`, you may want to use hooks instead which can be helpful when you want to decide if you want to render a feature programmatically or you want to evaluate multiple flags together instead of nesting a large tree of components.
117+
Alternatively to `LdFeature` or `LdMultivariate`, you may want to use hooks instead which can be helpful when you want to decide if you want to render a feature programmatically or you want to evaluate multiple flags together instead of nesting a large tree of components.
94118

95119
| Props | Type | Default | Description |
96120
| ------ | ---- | ------- | ----------- |

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-ld",
3-
"version": "0.1.3",
3+
"version": "0.2.0",
44
"description": "Connects a Launch Darkly client with a react tree",
55
"main": "lib/index.js",
66
"repository": "git@github.com:Tabcorp/react-ld.git",
@@ -35,7 +35,7 @@
3535
"eslint-plugin-jsx-a11y": "6.2.3",
3636
"eslint-plugin-react": "7.19.0",
3737
"eslint-plugin-react-hooks": "4.0.0",
38-
"flow-bin": "^0.131.0",
38+
"flow-bin": "^0.132.0",
3939
"flow-copy-source": "2.0.9",
4040
"husky": "^4.2.5",
4141
"jest": "^26.4.0",

src/Context.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import * as React from 'react';
33

44
export type FlagsT = {|
5-
[string]: boolean,
5+
[string]: any,
66
|}
77

88
export type ContextT = {|

src/LdFeature.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const LdFeature = ({
2121

2222
if (!flags) return fallback;
2323

24-
const isEnabled = flags[feature];
24+
const isEnabled = !!flags[feature];
2525
if (!isEnabled && !deprecation) return null;
2626
if (isEnabled && deprecation) return null;
2727

src/LdMultivariate.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @flow
2+
import * as React from 'react';
3+
4+
import Context from './Context';
5+
6+
type Props = {|
7+
children: (variation: any) => React.Node,
8+
feature: string,
9+
|};
10+
11+
const LdMultiVariate = ({
12+
children,
13+
feature,
14+
}: Props): React.Node => {
15+
const client = React.useContext(Context);
16+
const { flags } = client;
17+
18+
const variation = flags?.[feature];
19+
20+
return children(variation);
21+
};
22+
23+
export default LdMultiVariate;

src/LdMultivariate.spec.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// @flow
2+
import React from 'react';
3+
import { render, waitFor } from '@testing-library/react';
4+
5+
import { LdProvider, LdMultivariate } from '.';
6+
7+
describe('<LdMultivariate', () => {
8+
it('renders the render prop', (done) => {
9+
const hook = jest.fn();
10+
const fakeClient = {
11+
on: () => {},
12+
allFlags: jest.fn(() => ({
13+
enableTest: 'test',
14+
})),
15+
waitForInitialization: jest.fn(() => new Promise((resolve) => {
16+
resolve();
17+
hook();
18+
})),
19+
};
20+
const App = () => (
21+
<div data-testid="element">
22+
<LdMultivariate feature="enableTest">
23+
{(variation) => {
24+
if (variation === 'test') return '123';
25+
return '234';
26+
}}
27+
</LdMultivariate>
28+
</div>
29+
);
30+
31+
const { getByTestId } = render(
32+
<LdProvider
33+
client={fakeClient}
34+
>
35+
<App />
36+
</LdProvider>,
37+
);
38+
39+
waitFor(() => expect(hook).toHaveBeenCalled()).then(() => {
40+
expect(getByTestId('element').textContent).toBe('123');
41+
done();
42+
});
43+
});
44+
});

src/LdProvider.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const LdProvider = ({
4545
setFlags(client.allFlags());
4646
});
4747
client.on('change', (settings: SettingsT) => {
48-
setFlags((pFlags) => ({
48+
setFlags((pFlags: SettingsT) => ({
4949
...pFlags,
5050
...mapToCurrentFlags(settings),
5151
}));

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
export { default as Context } from './Context';
33
export { default as LdProvider } from './LdProvider';
44
export { default as LdFeature } from './LdFeature';
5+
export { default as LdMultivariate } from './LdMultivariate';
56
export { default as useLdFlag } from './useLdFlag';

src/useLdFlag.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as React from 'react';
33

44
import LdContext from './Context';
55

6-
export default function useLdFlag(name: string, fallback?: boolean = false): boolean {
6+
export default function useLdFlag<T = boolean>(name: string, fallback: T): T {
77
const { flags } = React.useContext(LdContext);
88
const feature = flags?.[name];
99

src/useLdFlag.spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,45 @@ describe('useLdFlag', () => {
8080
done();
8181
});
8282
});
83+
84+
it('can return multivariate value', (done) => {
85+
const hook = jest.fn();
86+
const fakeClient = {
87+
on: (key, callback) => {
88+
if (key === 'ready') {
89+
callback();
90+
}
91+
},
92+
allFlags: jest.fn(() => ({
93+
enableTest: 'string',
94+
})),
95+
waitForInitialization: jest.fn(() => new Promise((resolve) => {
96+
resolve();
97+
hook();
98+
})),
99+
};
100+
const App = () => {
101+
const enableTest: string = useLdFlag<string>('enableTest', '');
102+
103+
return (
104+
<div data-testid="element">
105+
{enableTest}
106+
</div>
107+
);
108+
};
109+
110+
const wrapper = render(
111+
<LdProvider
112+
client={fakeClient}
113+
async
114+
>
115+
<App />
116+
</LdProvider>,
117+
);
118+
119+
waitFor(() => expect(hook).toHaveBeenCalled()).then(() => {
120+
expect(wrapper.getByTestId('element').textContent).toBe('string');
121+
done();
122+
});
123+
});
83124
});

0 commit comments

Comments
 (0)