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;
}