Skip to content

Commit

Permalink
Provide default empty StoreObject for root IDs like ROOT_QUERY. (#7100)
Browse files Browse the repository at this point in the history
This change means the existence of root objects like ROOT_QUERY and
ROOT_MUTATION will no longer depend on whether other queries/mutations
have previously written data into the cache.

This matters because (until just recently) cache.read would return null
for a completely missing ROOT_QUERY object, but throw missing field errors
if ROOT_QUERY existed but did not have the requested fields.

In other words, this commit hides the difference between the absence of
ROOT_QUERY and its incompleteness, so unrelated cache writes can no longer
unexpectedly influence the null-returning vs. exception-throwing behavior
of cache.read.

As an additional motivation, with AC3 field policies, it's possible now to
define synthetic root query fields that have a chance of returning useful
values even if the cache is completely empty. Providing a default empty
object for root IDs like ROOT_QUERY is important to let those read
functions be called, instead of prematurely returning null from cache.read
when ROOT_QUERY does not exist.

Note: this PR builds on PR #7098.
  • Loading branch information
benjamn authored Sep 30, 2020
1 parent e387014 commit 1bd26ae
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
- In addition to the `result.data` property, `useQuery` and `useLazyQuery` will now provide a `result.previousData` property, which can be useful when a network request is pending and `result.data` is undefined, since `result.previousData` can be rendered instead of rendering an empty/loading state. <br/>
[@hwillson](https://github.com/hwillson) in [#7082](https://github.com/apollographql/apollo-client/pull/7082)

- Provide default empty cache object for root IDs like `ROOT_QUERY`, to avoid differences in behavior before/after `ROOT_QUERY` data has been written into `InMemoryCache`. <br/>
[@benjamn](https://github.com/benjamn) in [#7100](https://github.com/apollographql/apollo-client/pull/7100)

## Apollo Client 3.2.1

## Bug Fixes
Expand Down
52 changes: 52 additions & 0 deletions src/cache/inmemory/__tests__/readFromStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,58 @@ describe('reading from the store', () => {
});
});

it("read functions for root query fields work with empty cache", () => {
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
uuid() {
return "8d573b9c-cfcf-4e3e-98dd-14d255af577e";
},
null() {
return null;
},
}
},
},
});

expect(cache.readQuery({
query: gql`query { uuid null }`,
})).toEqual({
uuid: "8d573b9c-cfcf-4e3e-98dd-14d255af577e",
null: null,
});

expect(cache.extract()).toEqual({});

expect(cache.readFragment({
id: "ROOT_QUERY",
fragment: gql`
fragment UUIDFragment on Query {
null
uuid
}
`,
})).toEqual({
uuid: "8d573b9c-cfcf-4e3e-98dd-14d255af577e",
null: null,
});

expect(cache.extract()).toEqual({});

expect(cache.readFragment({
id: "does not exist",
fragment: gql`
fragment F on Never {
whatever
}
`,
})).toBe(null);

expect(cache.extract()).toEqual({});
});

it("custom read functions can map/filter dangling references", () => {
const cache = new InMemoryCache({
typePolicies: {
Expand Down
14 changes: 12 additions & 2 deletions src/cache/inmemory/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,18 @@ export abstract class EntityStore implements NormalizedCache {
// should not rely on this dependency, since the contents could change
// without the object being added or removed.
if (dependOnExistence) this.group.depend(dataId, "__exists");
return hasOwn.call(this.data, dataId) ? this.data[dataId] :
this instanceof Layer ? this.parent.lookup(dataId, dependOnExistence) : void 0;

if (hasOwn.call(this.data, dataId)) {
return this.data[dataId];
}

if (this instanceof Layer) {
return this.parent.lookup(dataId, dependOnExistence);
}

if (this.policies.rootTypenamesById[dataId]) {
return Object.create(null);
}
}

public merge(dataId: string, incoming: StoreObject): void {
Expand Down

0 comments on commit 1bd26ae

Please sign in to comment.