diff --git a/CHANGELOG.md b/CHANGELOG.md index 288ab54ac97..052c6689689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,9 @@ - Cancel `queryInfo.notifyTimeout` in `QueryInfo#markResult` to prevent unnecessary network requests when using a `FetchPolicy` of `cache-and-network` or `network-only` in a React component with multiple `useQuery` calls.
[@benjamn](https://github.com/benjamn) in [#7347](https://github.com/apollographql/apollo-client/pull/7347) +- `ApolloCache` objects (including `InMemoryCache`) may now be associated with or disassociated from individual reactive variables by calling `reactiveVar.attachCache(cache)` and/or `reactiveVar.forgetCache(cache)`.
+ [@benjamn](https://github.com/benjamn) in [#7350](https://github.com/apollographql/apollo-client/pull/7350) + ## Apollo Client 3.2.7 ## Bug Fixes diff --git a/package.json b/package.json index e6d96b13999..c669d22fa26 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ { "name": "apollo-client", "path": "./dist/apollo-client.cjs.min.js", - "maxSize": "25.4 kB" + "maxSize": "25.5 kB" } ], "peerDependencies": { diff --git a/src/cache/inmemory/__tests__/cache.ts b/src/cache/inmemory/__tests__/cache.ts index 212afe8bcc6..f58542e4449 100644 --- a/src/cache/inmemory/__tests__/cache.ts +++ b/src/cache/inmemory/__tests__/cache.ts @@ -2056,7 +2056,6 @@ describe("InMemoryCache#modify", () => { cache.modify({ fields: { comments(comments: Reference[], { readField }) { - debugger; expect(Object.isFrozen(comments)).toBe(true); expect(comments.length).toBe(3); const filtered = comments.filter(comment => { @@ -2757,6 +2756,133 @@ describe("ReactiveVar and makeVar", () => { }, ]); }); + + it('should broadcast to manually added caches', () => { + const rv = makeVar(0); + const cache = new InMemoryCache; + const query = gql`query { value }`; + const diffs: Cache.DiffResult[] = []; + const watch: Cache.WatchOptions = { + query, + optimistic: true, + callback(diff) { + diffs.push(diff); + }, + }; + + cache.writeQuery({ + query, + data: { + value: "oyez", + }, + }); + + const cancel = cache.watch(watch); + + // This should not trigger a broadcast, since we haven't associated + // this cache with rv yet. + rv(rv() + 1); + expect(diffs).toEqual([]); + + // The rv.attachCache method returns rv, for chaining. + rv.attachCache(cache)(rv() + 1); + + expect(diffs).toEqual([ + { + complete: true, + result: { + value: "oyez", + }, + }, + ]); + + cache.writeQuery({ + query, + broadcast: false, + data: { + value: "oyez, oyez", + }, + }); + + expect(diffs).toEqual([ + { + complete: true, + result: { + value: "oyez", + }, + }, + ]); + + rv(rv() + 1); + expect(diffs).toEqual([ + { + complete: true, + result: { + value: "oyez", + }, + }, + { + complete: true, + result: { + value: "oyez, oyez", + }, + }, + ]); + + expect(rv.forgetCache(cache)).toBe(true); + + cache.writeQuery({ + query, + broadcast: false, + data: { + value: "oyez, oyez, oyez", + }, + }); + + // Since we called rv.forgetCache(cache) above, updating rv here + // should not trigger a broadcast. + rv(rv() + 1); + expect(diffs).toEqual([ + { + complete: true, + result: { + value: "oyez", + }, + }, + { + complete: true, + result: { + value: "oyez, oyez", + }, + }, + ]); + + cache["broadcastWatches"](); + expect(diffs).toEqual([ + { + complete: true, + result: { + value: "oyez", + }, + }, + { + complete: true, + result: { + value: "oyez, oyez", + }, + }, + { + complete: true, + result: { + value: "oyez, oyez, oyez", + }, + }, + ]); + + cancel(); + + expect(rv()).toBe(4); + }); }); describe('TypedDocumentNode', () => { diff --git a/src/cache/inmemory/reactiveVars.ts b/src/cache/inmemory/reactiveVars.ts index d7b95c62832..409c8611837 100644 --- a/src/cache/inmemory/reactiveVars.ts +++ b/src/cache/inmemory/reactiveVars.ts @@ -6,6 +6,7 @@ import { ApolloCache } from '../../core'; export interface ReactiveVar { (newValue?: T): T; onNextChange(listener: ReactiveListener): () => void; + attachCache(cache: ApolloCache): this; forgetCache(cache: ApolloCache): boolean; } @@ -22,10 +23,12 @@ export const cacheSlot = new Slot>(); // to the original elements of the set before we begin iterating. See // iterateObserversSafely for another example of this pattern. function consumeAndIterate(set: Set, callback: (item: T) => any) { - const items: T[] = []; - set.forEach(item => items.push(item)); - set.clear(); - items.forEach(callback); + if (set.size) { + const items: T[] = []; + set.forEach(item => items.push(item)); + set.clear(); + items.forEach(callback); + } } const varsByCache = new WeakMap, Set>>(); @@ -61,12 +64,7 @@ export function makeVar(value: T): ReactiveVar { // context via cacheSlot. This isn't entirely foolproof, but it's // the same system that powers varDep. const cache = cacheSlot.getValue(); - if (cache) { - caches.add(cache); - let vars = varsByCache.get(cache)!; - if (!vars) varsByCache.set(cache, vars = new Set); - vars.add(rv); - } + if (cache) attach(cache); varDep(rv); } @@ -80,7 +78,22 @@ export function makeVar(value: T): ReactiveVar { }; }; - rv.forgetCache = cache => caches.delete(cache); + const attach = rv.attachCache = cache => { + caches.add(cache); + let vars = varsByCache.get(cache)!; + if (!vars) varsByCache.set(cache, vars = new Set); + vars.add(rv); + return rv; + }; + + rv.forgetCache = cache => { + const deleted = caches.delete(cache); + if (deleted) { + const vars = varsByCache.get(cache); + if (vars) vars.delete(rv); + } + return deleted; + }; return rv; }