Skip to content

Commit 99dac6c

Browse files
authored
Fix SuspenseWithPerf error (#154)
* fix test to reproduce error when a suspense child throws a promise more than once * create new trace every time the fallback renders
1 parent 0916a27 commit 99dac6c

File tree

2 files changed

+40
-32
lines changed

2 files changed

+40
-32
lines changed

reactfire/performance/index.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,15 @@ export function SuspenseWithPerf({
3636
}: SuspensePerfProps) {
3737
firePerf = firePerf || getPerfFromContext();
3838

39-
const trace = React.useRef(firePerf.trace(traceId));
40-
4139
const Fallback = () => {
4240
React.useLayoutEffect(() => {
43-
trace.current.start();
41+
const trace = firePerf.trace(traceId);
42+
trace.start();
4443

4544
return () => {
46-
trace.current.stop();
45+
trace.stop();
4746
};
48-
}, []);
47+
}, [traceId]);
4948

5049
return <>{fallback}</>;
5150
};

reactfire/performance/performance.test.tsx

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const mockFirebase = {
2222
performance: mockPerf
2323
};
2424

25+
const PromiseThrower = () => {
26+
throw new Promise((resolve, reject) => {});
27+
return <h1>Hello world</h1>;
28+
};
29+
2530
const Provider = ({ children }) => (
2631
<FirebaseAppProvider firebaseApp={mockFirebase}>
2732
{children}
@@ -82,10 +87,11 @@ describe('SuspenseWithPerf', () => {
8287

8388
it('creates a trace with the correct name', () => {
8489
const traceName = 'trace';
90+
8591
render(
8692
<Provider>
8793
<SuspenseWithPerf traceId={traceName} fallback={'loading'}>
88-
children
94+
<PromiseThrower />
8995
</SuspenseWithPerf>
9096
</Provider>
9197
);
@@ -139,7 +145,7 @@ describe('SuspenseWithPerf', () => {
139145
render(
140146
<Provider>
141147
<SuspenseWithPerf traceId={'hello'} fallback={'loading'}>
142-
{'children'}
148+
<PromiseThrower />
143149
</SuspenseWithPerf>
144150
</Provider>
145151
);
@@ -148,17 +154,24 @@ describe('SuspenseWithPerf', () => {
148154
});
149155

150156
it('can use firePerf from props', () => {
157+
const propPerf = mockPerf();
158+
propPerf.trace = jest.fn(() => ({
159+
start: traceStart,
160+
stop: traceEnd
161+
}));
151162
render(
152163
<SuspenseWithPerf
153164
traceId={'hello'}
154165
fallback={'loading'}
155-
firePerf={(mockPerf() as unknown) as performance.Performance}
166+
firePerf={(propPerf as unknown) as performance.Performance}
156167
>
157-
{'children'}
168+
<PromiseThrower />
158169
</SuspenseWithPerf>
159170
);
160171

161-
expect(createTrace).toHaveBeenCalled();
172+
// call the createTrace provided, not the one in context
173+
expect(propPerf.trace).toHaveBeenCalled();
174+
expect(createTrace).not.toHaveBeenCalled();
162175
});
163176

164177
it('Does not reuse a trace object', async () => {
@@ -180,39 +193,35 @@ describe('SuspenseWithPerf', () => {
180193
const o$ = new Subject();
181194

182195
const Comp = () => {
183-
useObservable(o$, 'test');
196+
const val = useObservable(o$, 'test');
197+
198+
if (val === 'throw') {
199+
throw new Promise(() => {});
200+
}
184201

185202
return <h1 data-testid="child">Actual</h1>;
186203
};
187204

188-
const Component = ({ renderPerf }) => {
189-
if (renderPerf) {
190-
return (
191-
<SuspenseWithPerf
192-
traceId={'hello'}
193-
fallback={'loading'}
194-
firePerf={(mockPerf() as unknown) as performance.Performance}
195-
>
196-
<Comp />
197-
</SuspenseWithPerf>
198-
);
199-
} else {
200-
return <div data-testid="other-element">no perf</div>;
201-
}
205+
const Component = () => {
206+
return (
207+
<SuspenseWithPerf
208+
traceId={'hello'}
209+
fallback={'loading'}
210+
firePerf={(mockPerf() as unknown) as performance.Performance}
211+
>
212+
<Comp />
213+
</SuspenseWithPerf>
214+
);
202215
};
203216

204217
// render SuspenseWithPerf and go through normal trace start -> trace stop
205-
const { getByTestId, rerender } = render(<Component renderPerf />);
218+
const { getByTestId, rerender } = render(<Component />);
206219
expect(createTrace).toHaveBeenCalledTimes(1);
207220
act(() => o$.next('some value'));
208221
await waitForElement(() => getByTestId('child'));
209222

210-
// re-render with changed props. now we're not rendering SuspenseWithPerf any more
211-
rerender(<Component renderPerf={false} />);
212-
await waitForElement(() => getByTestId('other-element'));
213-
214-
// re-render with changed props to render SuspenseWithPerf again
215-
rerender(<Component renderPerf />);
223+
// this is a magic value that will cause the child to throw a Promise again
224+
act(() => o$.next('throw'));
216225

217226
// if createTrace is only called once, the firebase SDK will throw
218227
expect(createTrace).toHaveBeenCalledTimes(2);

0 commit comments

Comments
 (0)