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

Change the default query data state from {} to undefined #3388

Merged
merged 1 commit into from Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions Changelog.md
@@ -1,13 +1,14 @@
# Change log

## vNext
## 3.1.0 (TBD)

### Bug Fixes

- Documentation fixes. <br/>
- Change the default query `data` state from `{}` to `undefined`. This change aligns all parts of the React Apollo query cycle so that `data` is always `undefined` if there is no data, instead of `data` being converted into an empty object. This change impacts the initial query response, initial SSR response, `data` value when errors occur, `data` value when skipping, etc. All of these areas are now aligned to only ever return a value for `data` if there really is a value to return (instead of making it seem like there is one by converting to `{}`). <br/>
[@hwillson](https://github.com/hwillson) in [#3388](https://github.com/apollographql/react-apollo/pull/3388)
- Documentation fixes. <br/>
[@SeanRoberts](https://github.com/SeanRoberts) in [#3380](https://github.com/apollographql/react-apollo/pull/3380)


## 3.0.1 (2019-08-15)

### Improvements
Expand Down
2 changes: 1 addition & 1 deletion packages/all/package.json
@@ -1,7 +1,7 @@
{
"name": "react-apollo",
"description": "React Apollo Hooks, Components, and HOC.",
"version": "3.0.1",
"version": "3.1.0-beta.0",
"author": "opensource@apollographql.com",
"keywords": [
"apollo",
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
@@ -1,7 +1,7 @@
{
"name": "@apollo/react-common",
"description": "React Apollo common utilities.",
"version": "3.0.1",
"version": "3.1.0-beta.0",
"author": "opensource@apollographql.com",
"keywords": [
"apollo",
Expand Down
2 changes: 1 addition & 1 deletion packages/components/package.json
@@ -1,7 +1,7 @@
{
"name": "@apollo/react-components",
"description": "React Apollo Query, Mutation and Subscription components.",
"version": "3.0.1",
"version": "3.1.0-beta.0",
"author": "opensource@apollographql.com",
"keywords": [
"apollo",
Expand Down
11 changes: 6 additions & 5 deletions packages/components/src/__tests__/client/Query.test.tsx
Expand Up @@ -9,7 +9,7 @@ import {
} from '@apollo/react-testing';
import { DocumentNode } from 'graphql';
import gql from 'graphql-tag';
import { render, cleanup } from '@testing-library/react';
import { render, cleanup, wait } from '@testing-library/react';
import { ApolloLink } from 'apollo-link';
import { Query } from '@apollo/react-components';

Expand Down Expand Up @@ -954,7 +954,7 @@ describe('Query component', () => {
);
});

it('onError with error', done => {
it('onError with error', async () => {
const error = new Error('error occurred');
const mockError = [
{
Expand All @@ -965,7 +965,6 @@ describe('Query component', () => {

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

const Component = () => (
Expand All @@ -981,6 +980,8 @@ describe('Query component', () => {
<Component />
</MockedProvider>
);

await wait();
});
});

Expand Down Expand Up @@ -1958,7 +1959,7 @@ describe('Query component', () => {
{(result: any) => {
const { data, loading } = result;
if (!loading) {
expect(data).toEqual({});
expect(data).toBeUndefined();
done();
}
return null;
Expand Down Expand Up @@ -2133,7 +2134,7 @@ describe('Query component', () => {
<ApolloProvider client={client}>
<Query query={partialQuery}>
{({ data }: any) => {
expect(data).toEqual({});
expect(data).toBeUndefined();
return null;
}}
</Query>
Expand Down
Expand Up @@ -21,7 +21,7 @@ Object {
exports[`Query component calls the children prop: result in render prop while loading 1`] = `
Object {
"called": true,
"data": Object {},
"data": undefined,
"error": undefined,
"fetchMore": [Function],
"loading": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/hoc/package.json
@@ -1,7 +1,7 @@
{
"name": "@apollo/react-hoc",
"description": "React Apollo `graphql` higher-order component.",
"version": "3.0.1",
"version": "3.1.0-beta.0",
"author": "opensource@apollographql.com",
"keywords": [
"apollo",
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/package.json
@@ -1,7 +1,7 @@
{
"name": "@apollo/react-hooks",
"description": "React Apollo Hooks.",
"version": "3.0.1",
"version": "3.1.0-beta.0",
"author": "opensource@apollographql.com",
"keywords": [
"apollo",
Expand Down
19 changes: 19 additions & 0 deletions packages/hooks/src/__tests__/useQuery.test.tsx
Expand Up @@ -58,6 +58,25 @@ describe('useQuery Hook', () => {
</MockedProvider>
);
});

it('should keep data as undefined until data is actually returned', done => {
const Component = () => {
const { data, loading } = useQuery(CAR_QUERY);
if (loading) {
expect(data).toBeUndefined();
} else {
expect(data).toEqual(CAR_RESULT_DATA);
done();
}
return null;
};

render(
<MockedProvider mocks={CAR_MOCKS}>
<Component />
</MockedProvider>
);
});
});

describe('Polling', () => {
Expand Down
46 changes: 25 additions & 21 deletions packages/hooks/src/data/QueryData.ts
Expand Up @@ -173,7 +173,7 @@ export class QueryData<TData, TVariables> extends OperationData {
loading: true,
networkStatus: NetworkStatus.loading,
called: true,
data: {}
data: undefined
};

if (this.context && this.context.renderPromises) {
Expand Down Expand Up @@ -275,7 +275,7 @@ export class QueryData<TData, TVariables> extends OperationData {
this.previousData.result &&
this.previousData.result.loading === loading &&
this.previousData.result.networkStatus === networkStatus &&
isEqual(this.previousData.result.data, data || {})
isEqual(this.previousData.result.data, data)
) {
return;
}
Expand Down Expand Up @@ -314,15 +314,9 @@ export class QueryData<TData, TVariables> extends OperationData {
}

private getQueryResult(): QueryResult<TData, TVariables> {
let result = {
data: Object.create(null) as TData
} as any;

// Attach bound methods
Object.assign(
result,
this.observableQueryFields(this.currentObservable.query!)
);
let result: any = {
...this.observableQueryFields(this.currentObservable.query!)
};

// When skipping a query (ie. we're not querying for data but still want
// to render children), make sure the `data` is cleared out and
Expand All @@ -340,21 +334,31 @@ export class QueryData<TData, TVariables> extends OperationData {
const currentResult = this.currentObservable.query!.getCurrentResult();
const { loading, partial, networkStatus, errors } = currentResult;
let { error, data } = currentResult;
data = data || (Object.create(null) as TData);

// Until a set naming convention for networkError and graphQLErrors is
// decided upon, we map errors (graphQLErrors) to the error options.
if (errors && errors.length > 0) {
error = new ApolloError({ graphQLErrors: errors });
}

Object.assign(result, { loading, networkStatus, error, called: true });
result = {
...result,
loading,
networkStatus,
error,
called: true
};

if (loading) {
const previousData = this.previousData.result
? this.previousData.result.data
: {};
Object.assign(result.data, previousData, data);
const previousData =
this.previousData.result && this.previousData.result.data;
result.data =
previousData && data
? {
...previousData,
...data
}
: previousData || data;
} else if (error) {
Object.assign(result, {
data: (this.currentObservable.query!.getLastResult() || ({} as any))
Expand All @@ -365,14 +369,14 @@ export class QueryData<TData, TVariables> extends OperationData {
const { partialRefetch } = this.getOptions();
if (
partialRefetch &&
Object.keys(data).length === 0 &&
!data &&
partial &&
fetchPolicy !== 'cache-only'
) {
// When a `Query` component is mounted, and a mutation is executed
// that returns the same ID as the mounted `Query`, but has less
// fields in its result, Apollo Client's `QueryManager` returns the
// data as an empty Object since a hit can't be found in the cache.
// data as `undefined` since a hit can't be found in the cache.
// This can lead to application errors when the UI elements rendered by
// the original `Query` component are expecting certain data values to
// exist, and they're all of a sudden stripped away. To help avoid
Expand All @@ -385,7 +389,7 @@ export class QueryData<TData, TVariables> extends OperationData {
return result;
}

Object.assign(result.data, data);
result.data = data;
}
}

Expand Down Expand Up @@ -417,7 +421,7 @@ export class QueryData<TData, TVariables> extends OperationData {
}

if (onCompleted && !error) {
onCompleted(data as TData);
onCompleted(data);
} else if (onError && error) {
onError(error);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ssr/package.json
@@ -1,7 +1,7 @@
{
"name": "@apollo/react-ssr",
"description": "React Apollo server-side rendering utilities",
"version": "3.0.1",
"version": "3.1.0-beta.0",
"author": "opensource@apollographql.com",
"keywords": [
"apollo",
Expand Down
4 changes: 2 additions & 2 deletions packages/ssr/src/__tests__/useQuery.test.tsx
Expand Up @@ -62,11 +62,11 @@ describe('useQuery Hook SSR', () => {
});
});

it('should initialize data as an empty object when loading', () => {
it('should initialize data as `undefined` when loading', () => {
const Component = () => {
const { data, loading } = useQuery(CAR_QUERY);
if (loading) {
expect(data).toEqual({});
expect(data).toBeUndefined();
}
return null;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/testing/package.json
@@ -1,7 +1,7 @@
{
"name": "@apollo/react-testing",
"description": "React Apollo testing utilities.",
"version": "3.0.1",
"version": "3.1.0-beta.0",
"author": "opensource@apollographql.com",
"keywords": [
"apollo",
Expand Down