Skip to content
This repository was archived by the owner on Apr 13, 2023. It is now read-only.

Commit 5e963a4

Browse files
MerzDanielhwillson
authored andcommitted
Add test for Query bug which is caused by changing props (#1749) (#2718)
* Add test for Query bug which is caused by changing props (#1749) * Skip the failing test (#1749) * Enable failing test * Leverage lastResult to limit unnecessary subscription re-creation When the Observable subscription created in `startQuerySubcription` receives an error, we only want to remove the old subscription, and start a new subscription, when we don't have a previous result stored. So if no previous result was received due to problems fetching data, or the previous result has been forcefully cleared out for some reason, we shoule re-create a new subscription. Otherwise we might be unnecessarily starting a new subscription the `Query` component isn't expecting, and isn't tying into its update cycle properly. * Changelog update
1 parent 5b765f9 commit 5e963a4

File tree

3 files changed

+121
-9
lines changed

3 files changed

+121
-9
lines changed

Changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
- Fix an infinite loop caused by using `setState` in the
88
`onError` / `onCompleted` callbacks of the `Query` component. <br/>
99
[@chenesan](https://github.com/chenesan) in [#2751](https://github.com/apollographql/react-apollo/pull/2751)
10+
- Fixed an issue that prevented good results from showing up in a `Query`
11+
component, after an error was received, variables were adjusted, and then
12+
the good data was fetched. <br/>
13+
[@MerzDaniel](https://github.com/MerzDaniel) in [#2718](https://github.com/apollographql/react-apollo/pull/2718)
1014

1115
### Improvements
1216

src/Query.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,15 @@ export default class Query<TData = any, TVariables = OperationVariables> extends
345345
this.updateCurrentData();
346346
},
347347
error: error => {
348-
this.resubscribeToQuery();
349-
// Quick fix for https://github.com/apollostack/react-apollo/issues/378
348+
if (!this.lastResult) {
349+
// We only want to remove the old subscription, and start a new
350+
// subscription, when an error was received and we don't have a
351+
// previous result stored. This means either no previous result was
352+
// received due to problems fetching data, or the previous result
353+
// has been forcefully cleared out.
354+
this.resubscribeToQuery();
355+
}
350356
if (!error.hasOwnProperty('graphQLErrors')) throw error;
351-
352357
this.updateCurrentData();
353358
},
354359
});
@@ -365,13 +370,15 @@ export default class Query<TData = any, TVariables = OperationVariables> extends
365370
private resubscribeToQuery() {
366371
this.removeQuerySubscription();
367372

373+
// Unfortunately, if `lastError` is set in the current
374+
// `queryObservable` when the subscription is re-created,
375+
// the subscription will immediately receive the error, which will
376+
// cause it to terminate again. To avoid this, we first clear
377+
// the last error/result from the `queryObservable` before re-starting
378+
// the subscription, and restore it afterwards (so the subscription
379+
// has a chance to stay open).
368380
const lastError = this.queryObservable!.getLastError();
369-
const lastResult = this.lastResult;
370-
371-
// If lastError is set, the observable will immediately
372-
// send it, causing the stream to terminate on initialization.
373-
// We clear everything here and restore it afterward to
374-
// make sure the new subscription sticks.
381+
const lastResult = this.queryObservable!.getLastResult();
375382
this.queryObservable!.resetLastResults();
376383
this.startQuerySubscription();
377384
Object.assign(this.queryObservable!, { lastError, lastResult });

test/client/Query.test.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,107 @@ describe('Query component', () => {
13871387
);
13881388
});
13891389

1390+
it(
1391+
'should not persist previous result errors when a subsequent valid ' +
1392+
'result is received',
1393+
done => {
1394+
const query: DocumentNode = gql`
1395+
query somethingelse ($variable: Boolean) {
1396+
allPeople(first: 1, yetisArePeople: $variable) {
1397+
people {
1398+
name
1399+
}
1400+
}
1401+
}`;
1402+
1403+
const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } };
1404+
const variableGood = { variable: true }
1405+
const variableBad = { variable: false }
1406+
1407+
const link = mockSingleLink(
1408+
{
1409+
request: {
1410+
query,
1411+
variables: variableGood,
1412+
},
1413+
result: {
1414+
data,
1415+
},
1416+
},
1417+
{
1418+
request: {
1419+
query,
1420+
variables: variableBad,
1421+
},
1422+
result: {
1423+
errors: [new Error('This is an error!')],
1424+
},
1425+
},
1426+
{
1427+
request: {
1428+
query,
1429+
variables: variableGood,
1430+
},
1431+
result: {
1432+
data,
1433+
},
1434+
},
1435+
);
1436+
1437+
const client = new ApolloClient({
1438+
link,
1439+
cache: new Cache({ addTypename: false }),
1440+
});
1441+
1442+
let count = 0;
1443+
const DummyComp = (props: any) => {
1444+
if (!props.loading) {
1445+
try {
1446+
switch (count++) {
1447+
case 0:
1448+
expect(props.data.allPeople).toBeTruthy();
1449+
expect(props.error).toBeFalsy();
1450+
// Change query variables to trigger bad result.
1451+
setTimeout(() => {
1452+
wrapper!.setProps({ variables: variableBad });
1453+
}, 0);
1454+
break;
1455+
case 1:
1456+
// Error should be received, but last known good value
1457+
// should still be accessible (in-case the UI needs it).
1458+
expect(props.error).toBeTruthy();
1459+
expect(props.data.allPeople).toBeTruthy();
1460+
// Change query variables to trigger a good result.
1461+
setTimeout(() => {
1462+
wrapper!.setProps({ variables: variableGood });
1463+
}, 0);
1464+
break
1465+
case 2:
1466+
// Good result should be received without any errors.
1467+
expect(props.error).toBeFalsy();
1468+
expect(props.data.allPeople).toBeTruthy();
1469+
done();
1470+
break;
1471+
default:
1472+
done.fail('Unknown count');
1473+
}
1474+
} catch (error) {
1475+
done.fail(error);
1476+
}
1477+
}
1478+
return null;
1479+
}
1480+
1481+
wrapper = mount(
1482+
<Query client={client} query={query} variables={variableGood}>
1483+
{(result: any) => {
1484+
return <DummyComp id='dummyId' {...result} />;
1485+
}}
1486+
</Query>
1487+
);
1488+
}
1489+
);
1490+
13901491
it('should not repeatedly call onCompleted if setState in it', done => {
13911492
const query = gql`
13921493
query people($first: Int) {

0 commit comments

Comments
 (0)