diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2160d1fa9b..27b179a7f3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,17 @@
- Removed `apollo-boost` since Apollo Client 3.0 provides a boost like getting started experience out of the box.
[@hwillson](https://github.com/hwillson) in [#5217](https://github.com/apollographql/apollo-client/pull/5217)
+- `InMemoryCache` provides a new API for storing local state that can be easily updated by external code:
+ ```ts
+ const lv = cache.makeLocalVar(123)
+ console.log(lv()) // 123
+ console.log(lv(lv() + 1)) // 124
+ console.log(lv()) // 124
+ lv("asdf") // TS type error
+ ```
+ These local variables are _reactive_ in the sense that updating their values invalidates any previously cached query results that depended on the old values.
+ [@benjamn](https://github.com/benjamn) in [#5799](https://github.com/apollographql/apollo-client/pull/5799)
+
- The `queryManager` property of `ApolloClient` instances is now marked as
`private`, paving the way for a more aggressive redesign of its API.
diff --git a/src/cache/inmemory/__tests__/cache.ts b/src/cache/inmemory/__tests__/cache.ts
index d4a59790acf..e35469d9960 100644
--- a/src/cache/inmemory/__tests__/cache.ts
+++ b/src/cache/inmemory/__tests__/cache.ts
@@ -1324,3 +1324,110 @@ describe("InMemoryCache#broadcastWatches", function () {
]);
});
});
+
+describe("cache.makeLocalVar", () => {
+ function makeCacheAndVar(resultCaching: boolean) {
+ const cache: InMemoryCache = new InMemoryCache({
+ resultCaching,
+ typePolicies: {
+ Person: {
+ fields: {
+ name() {
+ return nameVar();
+ },
+ },
+ },
+ },
+ });
+
+ const nameVar = cache.makeLocalVar("Ben");
+
+ const query = gql`
+ query {
+ onCall {
+ name
+ }
+ }
+ `;
+
+ cache.writeQuery({
+ query,
+ data: {
+ onCall: {
+ __typename: "Person",
+ },
+ },
+ });
+
+ return {
+ cache,
+ nameVar,
+ query,
+ };
+ }
+
+ it("should work with resultCaching enabled (default)", () => {
+ const { cache, nameVar, query } = makeCacheAndVar(true);
+
+ const result1 = cache.readQuery({ query });
+ expect(result1).toEqual({
+ onCall: {
+ __typename: "Person",
+ name: "Ben",
+ },
+ });
+
+ // No change before updating the nameVar.
+ expect(cache.readQuery({ query })).toBe(result1);
+
+ expect(nameVar()).toBe("Ben");
+ expect(nameVar("Hugh")).toBe("Hugh");
+
+ const result2 = cache.readQuery({ query });
+ expect(result2).not.toBe(result1);
+ expect(result2).toEqual({
+ onCall: {
+ __typename: "Person",
+ name: "Hugh",
+ },
+ });
+
+ expect(nameVar()).toBe("Hugh");
+ expect(nameVar("James")).toBe("James");
+
+ expect(cache.readQuery({ query })).toEqual({
+ onCall: {
+ __typename: "Person",
+ name: "James",
+ },
+ });
+ });
+
+ it("should work with resultCaching disabled (unusual)", () => {
+ const { cache, nameVar, query } = makeCacheAndVar(false);
+
+ const result1 = cache.readQuery({ query });
+ expect(result1).toEqual({
+ onCall: {
+ __typename: "Person",
+ name: "Ben",
+ },
+ });
+
+ const result2 = cache.readQuery({ query });
+ // Without resultCaching, equivalent results will not be ===.
+ expect(result2).not.toBe(result1);
+ expect(result2).toEqual(result1);
+
+ expect(nameVar()).toBe("Ben");
+ expect(nameVar("Hugh")).toBe("Hugh");
+
+ const result3 = cache.readQuery({ query });
+ expect(result3).toEqual({
+ onCall: {
+ __typename: "Person",
+ name: "Hugh",
+ },
+ });
+ });
+});
diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts
index c139e43e5ad..82d8c96a9e6 100644
--- a/src/cache/inmemory/inMemoryCache.ts
+++ b/src/cache/inmemory/inMemoryCache.ts
@@ -2,7 +2,7 @@
import './fixPolyfills';
import { DocumentNode } from 'graphql';
-import { wrap } from 'optimism';
+import { dep, wrap } from 'optimism';
import { ApolloCache, Transaction } from '../core/cache';
import { Cache } from '../core/types/Cache';
@@ -298,4 +298,21 @@ export class InMemoryCache extends ApolloCache {
}),
);
}
+
+ public makeLocalVar(value: T): LocalVar {
+ return function LocalVar(newValue) {
+ if (arguments.length > 0) {
+ if (value !== newValue) {
+ value = newValue;
+ localVarDep.dirty(LocalVar);
+ }
+ } else {
+ localVarDep(LocalVar);
+ }
+ return value;
+ };
+ }
}
+
+const localVarDep = dep>();
+export type LocalVar = (newValue?: T) => T;