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

Commit

Permalink
Merge pull request #1922 from jeshep/master
Browse files Browse the repository at this point in the history
Add `onCompleted` and `onError` props to `Query`
  • Loading branch information
hwillson committed Jun 29, 2018
2 parents 0b9120f + 198cc9d commit 55e0392
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 5 deletions.
9 changes: 8 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Change log

## 2.1.8 (Jun3 28, 2018)
## vNext

- Added `onCompleted` and `onError` props to the `Query` component, than can
be used to register callback functions that are to be executed after a
query successfully completes, or an error occurs.
[@jeshep](https://github.com/jeshep) in [#1922](https://github.com/apollographql/react-apollo/pull/1922)

## 2.1.8 (June 28, 2018)

- Addressed deployment issue.

Expand Down
22 changes: 19 additions & 3 deletions src/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export interface QueryProps<TData = any, TVariables = OperationVariables> {
skip?: boolean;
client?: ApolloClient<Object>;
context?: Record<string, any>;
onCompleted?: (data: TData | {}) => void;
onError?: (error: ApolloError) => void;
}

export interface QueryContext {
Expand All @@ -132,6 +134,8 @@ export default class Query<TData = any, TVariables = OperationVariables> extends
children: PropTypes.func.isRequired,
fetchPolicy: PropTypes.string,
notifyOnNetworkStatusChange: PropTypes.bool,
onCompleted: PropTypes.func,
onError: PropTypes.func,
pollInterval: PropTypes.number,
query: PropTypes.object.isRequired,
variables: PropTypes.object,
Expand Down Expand Up @@ -171,8 +175,9 @@ export default class Query<TData = any, TVariables = OperationVariables> extends
// For server-side rendering (see getDataFromTree.ts)
fetchData(): Promise<ApolloQueryResult<any>> | boolean {
if (this.props.skip) return false;

// pull off react options
const { children, ssr, displayName, skip, client, ...opts } = this.props;
const { children, ssr, displayName, skip, client, onCompleted, onError, ...opts } = this.props;

let { fetchPolicy } = opts;
if (ssr === false) return false;
Expand Down Expand Up @@ -307,7 +312,7 @@ export default class Query<TData = any, TVariables = OperationVariables> extends

private startQuerySubscription = () => {
if (this.querySubscription) return;
// store the inital renders worth of result
// store the initial renders worth of result
let current: QueryResult<TData, TVariables> | undefined = this.getQueryResult();
this.querySubscription = this.queryObservable!.subscribe({
next: () => {
Expand Down Expand Up @@ -352,6 +357,17 @@ export default class Query<TData = any, TVariables = OperationVariables> extends
}

private updateCurrentData = () => {
const { onCompleted, onError } = this.props;
if (onCompleted || onError) {
const currentResult = this.queryObservable!.currentResult();
const { loading, error, data } = currentResult;
if (onCompleted && !loading && !error) {
onCompleted(data);
} else if (onError && !loading && error) {
onError(error);
}
}

// force a rerender that goes through shouldComponentUpdate
if (this.hasMounted) this.forceUpdate();
};
Expand Down Expand Up @@ -394,7 +410,7 @@ export default class Query<TData = any, TVariables = OperationVariables> extends
// If a subscription has not started, then the synchronous call to refetch
// must be made at a time when an active network request is being made, so
// we ensure that the network requests are deduped, to avoid an
// inconsistant UI state that displays different data for the current query
// inconsistent UI state that displays different data for the current query
// alongside a refetched query.
//
// Once the Query component is mounted and the subscription is made, we
Expand Down
124 changes: 123 additions & 1 deletion test/client/Query.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import ApolloClient, { NetworkStatus } from 'apollo-client';
import ApolloClient, { ApolloError, NetworkStatus } from 'apollo-client';
import { mount, ReactWrapper } from 'enzyme';
import { InMemoryCache as Cache } from 'apollo-cache-inmemory';
import { ApolloProvider, Query } from '../../src';
Expand Down Expand Up @@ -688,6 +688,70 @@ describe('Query component', () => {
done();
});
});

it('onCompleted with data', done => {
const mocks = [
{
request: { query: allPeopleQuery },
result: { data: allPeopleData },
},
];

const onCompleted = (queryData: Data | {}) => {
expect(stripSymbols(queryData)).toEqual(allPeopleData);
done();
};

const Component = () => (
<Query query={allPeopleQuery} onCompleted={onCompleted}>
{() => {
return null;
}}
</Query>
);

wrapper = mount(
<MockedProvider mocks={mocks} addTypename={false}>
<Component />
</MockedProvider>,
);
});

it('onError with data', done => {
const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } };

const mocks = [
{
request: { query: allPeopleQuery },
result: { data: data },
},
];

const onErrorFunc = (queryError: ApolloError) => {
expect(queryError).toEqual(null);
done();
};

const onError = jest.fn();

const Component = () => (
<Query query={allPeopleQuery} onError={onErrorFunc}>
{({ loading }) => {
if (!loading) {
expect(onError).not.toHaveBeenCalled();
done();
}
return null;
}}
</Query>
);

wrapper = mount(
<MockedProvider mocks={mocks} addTypename={false}>
<Component />
</MockedProvider>,
);
});
});

describe('props disallow', () => {
Expand Down Expand Up @@ -737,6 +801,64 @@ describe('Query component', () => {

console.error = errorLogger;
});

it('onCompleted with error', done => {
const mockError = [
{
request: { query: allPeopleQuery },
error: new Error('error occurred'),
},
];

const onCompleted = jest.fn();

const Component = () => (
<Query query={allPeopleQuery} onCompleted={onCompleted}>
{({ error }) => {
if (error) {
expect(onCompleted).not.toHaveBeenCalled();
done();
}
return null;
}}
</Query>
);

wrapper = mount(
<MockedProvider mocks={mockError} addTypename={false}>
<Component />
</MockedProvider>,
);
});

it('onError with error', done => {
const error = new Error('error occurred');
const mockError = [
{
request: { query: allPeopleQuery },
error: error,
},
];

const onErrorFunc = (queryError: ApolloError) => {
expect(queryError.networkError).toEqual(error);
done();
};

const Component = () => (
<Query query={allPeopleQuery} onError={onErrorFunc}>
{() => {
return null;
}}
</Query>
);

wrapper = mount(
<MockedProvider mocks={mockError} addTypename={false}>
<Component />
</MockedProvider>,
);
});
});

describe('should update', () => {
Expand Down

0 comments on commit 55e0392

Please sign in to comment.