Skip to content

Commit

Permalink
Implement reactive variables for tracking local state. (#5799)
Browse files Browse the repository at this point in the history
Local variables are functions that allow you to store local state in a
well-known, private location, an update it whenever you like:

  const lv = cache.makeLocalVar(123)
  console.log(lv()) // 123
  console.log(lv(lv() + 1)) // 124
  console.log(lv()) // 124
  lv("asdf") // type error

Any parts of any queries that relied upon the value of the variable will
be invalidated when/if the variable's value is updated.
  • Loading branch information
benjamn authored Jan 17, 2020
1 parent cc540b6 commit a8288e0
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@
- Removed `apollo-boost` since Apollo Client 3.0 provides a boost like getting started experience out of the box. <br/>
[@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. <br/>
[@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.

Expand Down
107 changes: 107 additions & 0 deletions src/cache/inmemory/__tests__/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
});
});
});
19 changes: 18 additions & 1 deletion src/cache/inmemory/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -298,4 +298,21 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
}),
);
}

public makeLocalVar<T>(value: T): LocalVar<T> {
return function LocalVar(newValue) {
if (arguments.length > 0) {
if (value !== newValue) {
value = newValue;
localVarDep.dirty(LocalVar);
}
} else {
localVarDep(LocalVar);
}
return value;
};
}
}

const localVarDep = dep<LocalVar<any>>();
export type LocalVar<T> = (newValue?: T) => T;

0 comments on commit a8288e0

Please sign in to comment.