Skip to content

Commit 14f657a

Browse files
author
James Fox
committed
convert <OptimizelyFeature> component to functional component that uses useFeature
1 parent b7ae79f commit 14f657a

File tree

2 files changed

+37
-150
lines changed

2 files changed

+37
-150
lines changed

src/Feature.spec.tsx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ describe('<OptimizelyFeature>', () => {
8181

8282
component.update();
8383

84-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1');
85-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1');
84+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined);
85+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined);
8686
expect(component.text()).toBe('true|bar');
8787
});
8888

@@ -99,14 +99,14 @@ describe('<OptimizelyFeature>', () => {
9999

100100
// while it's waiting for onReady()
101101
expect(component.text()).toBe('');
102-
resolver.resolve({ sucess: true });
102+
resolver.resolve({ success: true });
103103

104104
await optimizelyMock.onReady();
105105

106106
component.update();
107107

108-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1');
109-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1');
108+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined);
109+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined);
110110
expect(component.text()).toBe('true|bar');
111111
});
112112

@@ -123,14 +123,14 @@ describe('<OptimizelyFeature>', () => {
123123

124124
// while it's waiting for onReady()
125125
expect(component.text()).toBe('');
126-
resolver.resolve({ sucess: true });
126+
resolver.resolve({ success: true });
127127

128128
await optimizelyMock.onReady();
129129

130130
component.update();
131131

132-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1');
133-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1');
132+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined);
133+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined);
134134
expect(component.text()).toBe('true|bar');
135135
});
136136

@@ -148,14 +148,14 @@ describe('<OptimizelyFeature>', () => {
148148

149149
// while it's waiting for onReady()
150150
expect(component.text()).toBe('');
151-
resolver.resolve({ sucess: true });
151+
resolver.resolve({ success: true });
152152

153153
await optimizelyMock.onReady();
154154

155155
component.update();
156156

157-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1');
158-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1');
157+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined);
158+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined);
159159
expect(component.text()).toBe('true|bar');
160160

161161
const updateFn = (optimizelyMock.notificationCenter.addNotificationListener as jest.Mock).mock.calls[0][1];
@@ -190,14 +190,14 @@ describe('<OptimizelyFeature>', () => {
190190

191191
// while it's waiting for onReady()
192192
expect(component.text()).toBe('');
193-
resolver.resolve({ sucess: true });
193+
resolver.resolve({ success: true });
194194

195195
await optimizelyMock.onReady();
196196

197197
component.update();
198198

199-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1');
200-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1');
199+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined);
200+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined);
201201
expect(component.text()).toBe('true|bar');
202202

203203
const updateFn = (optimizelyMock.onUserUpdate as jest.Mock).mock.calls[0][0];
@@ -233,14 +233,14 @@ describe('<OptimizelyFeature>', () => {
233233

234234
// while it's waiting for onReady()
235235
expect(component.text()).toBe('');
236-
resolver.resolve({ sucess: false, reason: 'fail' });
236+
resolver.resolve({ success: false, reason: 'fail', dataReadyPromise: Promise.resolve() });
237237

238-
await optimizelyMock.onReady();
238+
await optimizelyMock.onReady().then(res => res.dataReadyPromise);
239239

240240
component.update();
241241

242-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1');
243-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1');
242+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined);
243+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined);
244244
expect(component.text()).toBe('true|bar');
245245
});
246246
});

src/Feature.tsx

Lines changed: 19 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -14,148 +14,35 @@
1414
* limitations under the License.
1515
*/
1616
import * as React from 'react';
17-
import { withOptimizely, WithOptimizelyProps } from './withOptimizely';
18-
import { VariableValuesObject, OnReadyResult, DEFAULT_ON_READY_TIMEOUT } from './client';
19-
import { getLogger } from '@optimizely/js-sdk-logging';
17+
import { UserAttributes } from '@optimizely/optimizely-sdk';
2018

21-
const logger = getLogger('<OptimizelyFeature>');
19+
import { VariableValuesObject } from './client';
20+
import { useFeature } from './hooks';
21+
import { withOptimizely, WithOptimizelyProps } from './withOptimizely';
2222

2323
export interface FeatureProps extends WithOptimizelyProps {
24-
// TODO add support for overrideUserId
2524
feature: string;
2625
timeout?: number;
2726
autoUpdate?: boolean;
27+
overrideUserId?: string;
28+
overrideAttributes?: UserAttributes;
2829
children: (isEnabled: boolean, variables: VariableValuesObject) => React.ReactNode;
2930
}
3031

31-
export interface FeatureState {
32-
canRender: boolean;
33-
isEnabled: boolean;
34-
variables: VariableValuesObject;
35-
}
36-
37-
class FeatureComponent extends React.Component<FeatureProps, FeatureState> {
38-
private optimizelyNotificationId?: number;
39-
private unregisterUserListener: () => void;
40-
private autoUpdate = false;
41-
42-
constructor(props: FeatureProps) {
43-
super(props);
44-
45-
this.unregisterUserListener = () => {};
46-
47-
const { autoUpdate, isServerSide, optimizely, feature } = props;
48-
this.autoUpdate = !!autoUpdate;
49-
if (isServerSide) {
50-
if (optimizely === null) {
51-
throw new Error('optimizely prop must be supplied');
52-
}
53-
const isEnabled = optimizely.isFeatureEnabled(feature);
54-
const variables = optimizely.getFeatureVariables(feature);
55-
this.state = {
56-
canRender: true,
57-
isEnabled,
58-
variables,
59-
};
60-
} else {
61-
this.state = {
62-
canRender: false,
63-
isEnabled: false,
64-
variables: {},
65-
};
66-
}
67-
}
68-
69-
componentDidMount() {
70-
const { feature, optimizely, optimizelyReadyTimeout, isServerSide, timeout } = this.props;
71-
if (!optimizely) {
72-
throw new Error('optimizely prop must be supplied');
73-
}
74-
75-
if (isServerSide) {
76-
return;
77-
}
78-
79-
// allow overriding of the ready timeout via the `timeout` prop passed to <Experiment />
80-
const finalReadyTimeout: number | undefined = timeout !== undefined ? timeout : optimizelyReadyTimeout;
81-
82-
optimizely.onReady({ timeout: finalReadyTimeout }).then((res: OnReadyResult) => {
83-
if (res.success) {
84-
logger.info('feature="%s" successfully rendered for user="%s"', feature, optimizely.user.id);
85-
} else {
86-
logger.info(
87-
'feature="%s" could not be checked before timeout of %sms, reason="%s" ',
88-
feature,
89-
timeout === undefined ? DEFAULT_ON_READY_TIMEOUT : timeout,
90-
res.reason || ''
91-
);
92-
}
93-
94-
const isEnabled = optimizely.isFeatureEnabled(feature);
95-
const variables = optimizely.getFeatureVariables(feature);
96-
this.setState({
97-
canRender: true,
98-
isEnabled,
99-
variables,
100-
});
101-
102-
if (this.autoUpdate) {
103-
this.setupAutoUpdateListeners();
104-
}
105-
});
106-
}
107-
108-
setupAutoUpdateListeners() {
109-
const { optimizely, feature } = this.props;
110-
if (optimizely === null) {
111-
return;
112-
}
113-
114-
this.optimizelyNotificationId = optimizely.notificationCenter.addNotificationListener(
115-
'OPTIMIZELY_CONFIG_UPDATE',
116-
() => {
117-
logger.info('OPTIMIZELY_CONFIG_UPDATE, re-evaluating feature="%s" for user="%s"', feature, optimizely.user.id);
118-
const isEnabled = optimizely.isFeatureEnabled(feature);
119-
const variables = optimizely.getFeatureVariables(feature);
120-
this.setState({
121-
isEnabled,
122-
variables,
123-
});
124-
}
125-
);
126-
127-
this.unregisterUserListener = optimizely.onUserUpdate(() => {
128-
logger.info('User update, re-evaluating feature="%s" for user="%s"', feature, optimizely.user.id);
129-
const isEnabled = optimizely.isFeatureEnabled(feature);
130-
const variables = optimizely.getFeatureVariables(feature);
131-
this.setState({
132-
isEnabled,
133-
variables,
134-
});
135-
});
32+
const FeatureComponent = (props: FeatureProps): any => {
33+
const { feature, timeout, autoUpdate, children, overrideUserId, overrideAttributes } = props;
34+
const [isEnabled, variables, clientReady, didTimeout] = useFeature(
35+
feature,
36+
{ timeout, autoUpdate },
37+
{ overrideUserId, overrideAttributes }
38+
);
39+
40+
if (!clientReady && !didTimeout) {
41+
// Only block rendering while were waiting for the client within the allowed timeout.
42+
return null;
13643
}
13744

138-
componentWillUnmount() {
139-
const { optimizely, isServerSide } = this.props;
140-
if (isServerSide || !this.autoUpdate) {
141-
return;
142-
}
143-
if (optimizely && this.optimizelyNotificationId) {
144-
optimizely.notificationCenter.removeNotificationListener(this.optimizelyNotificationId);
145-
}
146-
this.unregisterUserListener();
147-
}
148-
149-
render() {
150-
const { children } = this.props;
151-
const { isEnabled, variables, canRender } = this.state;
152-
153-
if (!canRender) {
154-
return null;
155-
}
156-
157-
return children(isEnabled, variables);
158-
}
159-
}
45+
return children(isEnabled, variables);
46+
};
16047

16148
export const OptimizelyFeature = withOptimizely(FeatureComponent);

0 commit comments

Comments
 (0)