diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd32e939442..8870c4553b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
## Apollo Client 3.5.0 (not yet released)
+### Improvements
+
+- Add `updateQuery` and `updateFragment` methods to `ApolloCache`, simplifying common `readQuery`/`writeQuery` cache update patterns.
+ [@wassim-k](/~https://github.com/wassim-k) in [#8382](/~https://github.com/apollographql/apollo-client/pull/8382)
+
### React Refactoring
#### Bug Fixes (due to [@brainkim](/~https://github.com/brainkim) in [#8596](/~https://github.com/apollographql/apollo-client/pull/8596)):
diff --git a/package.json b/package.json
index 54e4c20b02d..928fae6e0a1 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
{
"name": "apollo-client",
"path": "./temp/bundlesize.min.cjs",
- "maxSize": "24.7 kB"
+ "maxSize": "24.75 kB"
}
],
"engines": {
diff --git a/src/cache/core/__tests__/cache.ts b/src/cache/core/__tests__/cache.ts
index f1417add255..baa090c1af4 100644
--- a/src/cache/core/__tests__/cache.ts
+++ b/src/cache/core/__tests__/cache.ts
@@ -1,5 +1,5 @@
import gql from 'graphql-tag';
-import { ApolloCache } from '../cache';
+import { ApolloCache } from '../cache';
import { Cache, DataProxy } from '../..';
import { Reference } from '../../../utilities/graphql/storeUtils';
@@ -73,7 +73,7 @@ describe('abstract cache', () => {
const test = new TestCache();
test.read = jest.fn();
- test.readQuery({query});
+ test.readQuery({ query });
expect(test.read).toBeCalled();
});
@@ -81,8 +81,8 @@ describe('abstract cache', () => {
const test = new TestCache();
test.read = ({ optimistic }) => optimistic as any;
- expect(test.readQuery({query})).toBe(false);
- expect(test.readQuery({query}, true)).toBe(true);
+ expect(test.readQuery({ query })).toBe(false);
+ expect(test.readQuery({ query }, true)).toBe(true);
});
});
@@ -151,4 +151,159 @@ describe('abstract cache', () => {
expect(test.write).toBeCalled();
});
});
+
+ describe('updateQuery', () => {
+ it('runs the readQuery & writeQuery methods', () => {
+ const test = new TestCache();
+ test.readQuery = jest.fn();
+ test.writeQuery = jest.fn();
+
+ test.updateQuery({ query }, data => 'foo');
+
+ expect(test.readQuery).toBeCalled();
+ expect(test.writeQuery).toBeCalled();
+ });
+
+ it('does not call writeQuery method if data is null', () => {
+ const test = new TestCache();
+ test.readQuery = jest.fn();
+ test.writeQuery = jest.fn();
+
+ test.updateQuery({ query }, data => null);
+
+ expect(test.readQuery).toBeCalled();
+ expect(test.writeQuery).not.toBeCalled();
+ });
+
+ it('does not call writeQuery method if data is undefined', () => {
+ const test = new TestCache();
+ test.readQuery = jest.fn();
+ test.writeQuery = jest.fn();
+
+ test.updateQuery({ query }, data => { return; });
+
+ expect(test.readQuery).toBeCalled();
+ expect(test.writeQuery).not.toBeCalled();
+ });
+
+ it('calls the readQuery & writeQuery methods with the options object', () => {
+ const test = new TestCache();
+ const options: Cache.UpdateQueryOptions = { query, broadcast: true, variables: { test: 1 }, optimistic: true, returnPartialData: true };
+ test.readQuery = jest.fn();
+ test.writeQuery = jest.fn();
+
+ test.updateQuery(options, data => 'foo');
+
+ expect(test.readQuery).toBeCalledWith(
+ expect.objectContaining(options)
+ );
+
+ expect(test.writeQuery).toBeCalledWith(
+ expect.objectContaining({ ...options, data: 'foo' })
+ );
+ });
+
+ it('returns current value in memory if no update was made', () => {
+ const test = new TestCache();
+ test.readQuery = jest.fn().mockReturnValue('foo');
+ expect(test.updateQuery({ query }, data => null)).toBe('foo');
+ });
+
+ it('returns the updated value in memory if an update was made', () => {
+ const test = new TestCache();
+ let currentValue = 'foo';
+ test.readQuery = jest.fn().mockImplementation(() => currentValue);
+ test.writeQuery = jest.fn().mockImplementation(({ data }) => currentValue = data);
+ expect(test.updateQuery({ query }, data => 'bar')).toBe('bar');
+ });
+
+ it('calls update function with the current value in memory', () => {
+ const test = new TestCache();
+ test.readQuery = jest.fn().mockReturnValue('foo');
+ test.updateQuery({ query }, data => {
+ expect(data).toBe('foo');
+ });
+ });
+ });
+
+ describe('updateFragment', () => {
+ const fragmentId = 'frag';
+ const fragment = gql`
+ fragment a on b {
+ name
+ }
+ `;
+
+ it('runs the readFragment & writeFragment methods', () => {
+ const test = new TestCache();
+ test.readFragment = jest.fn();
+ test.writeFragment = jest.fn();
+
+ test.updateFragment({ id: fragmentId, fragment }, data => 'foo');
+
+ expect(test.readFragment).toBeCalled();
+ expect(test.writeFragment).toBeCalled();
+ });
+
+ it('does not call writeFragment method if data is null', () => {
+ const test = new TestCache();
+ test.readFragment = jest.fn();
+ test.writeFragment = jest.fn();
+
+ test.updateFragment({ id: fragmentId, fragment }, data => null);
+
+ expect(test.readFragment).toBeCalled();
+ expect(test.writeFragment).not.toBeCalled();
+ });
+
+ it('does not call writeFragment method if data is undefined', () => {
+ const test = new TestCache();
+ test.readFragment = jest.fn();
+ test.writeFragment = jest.fn();
+
+ test.updateFragment({ id: fragmentId, fragment }, data => { return; });
+
+ expect(test.readFragment).toBeCalled();
+ expect(test.writeFragment).not.toBeCalled();
+ });
+
+ it('calls the readFragment & writeFragment methods with the options object', () => {
+ const test = new TestCache();
+ const options: Cache.UpdateFragmentOptions = { id: fragmentId, fragment, fragmentName: 'a', broadcast: true, variables: { test: 1 }, optimistic: true, returnPartialData: true };
+ test.readFragment = jest.fn();
+ test.writeFragment = jest.fn();
+
+ test.updateFragment(options, data => 'foo');
+
+ expect(test.readFragment).toBeCalledWith(
+ expect.objectContaining(options)
+ );
+
+ expect(test.writeFragment).toBeCalledWith(
+ expect.objectContaining({ ...options, data: 'foo' })
+ );
+ });
+
+ it('returns current value in memory if no update was made', () => {
+ const test = new TestCache();
+ test.readFragment = jest.fn().mockReturnValue('foo');
+ expect(test.updateFragment({ id: fragmentId, fragment }, data => { return; })).toBe('foo');
+ });
+
+ it('returns the updated value in memory if an update was made', () => {
+ const test = new TestCache();
+ let currentValue = 'foo';
+ test.readFragment = jest.fn().mockImplementation(() => currentValue);
+ test.writeFragment = jest.fn().mockImplementation(({ data }) => currentValue = data);
+ expect(test.updateFragment({ id: fragmentId, fragment }, data => 'bar')).toBe('bar');
+ });
+
+ it('calls update function with the current value in memory', () => {
+ const test = new TestCache();
+ test.readFragment = jest.fn().mockReturnValue('foo');
+ test.updateFragment({ id: fragmentId, fragment }, data => {
+ expect(data).toBe('foo');
+ });
+ });
+ });
});
diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts
index db5c63879a8..f15a142f8f8 100644
--- a/src/cache/core/cache.ts
+++ b/src/cache/core/cache.ts
@@ -166,4 +166,26 @@ export abstract class ApolloCache implements DataProxy {
result: data,
}));
}
+
+ public updateQuery(
+ options: Cache.UpdateQueryOptions,
+ update: (data: TData | null) => TData | null | void,
+ ): TData | null {
+ const value = this.readQuery(options);
+ const data = update(value);
+ if (data === void 0 || data === null) return value;
+ this.writeQuery({ ...options, data });
+ return data;
+ }
+
+ public updateFragment(
+ options: Cache.UpdateFragmentOptions,
+ update: (data: TData | null) => TData | null | void,
+ ): TData | null {
+ const value = this.readFragment(options);
+ const data = update(value);
+ if (data === void 0 || data === null) return value;
+ this.writeFragment({ ...options, data });
+ return data;
+ }
}
diff --git a/src/cache/core/types/Cache.ts b/src/cache/core/types/Cache.ts
index 98a21afd4f5..fe9b9d83bbb 100644
--- a/src/cache/core/types/Cache.ts
+++ b/src/cache/core/types/Cache.ts
@@ -92,5 +92,7 @@ export namespace Cache {
export import ReadFragmentOptions = DataProxy.ReadFragmentOptions;
export import WriteQueryOptions = DataProxy.WriteQueryOptions;
export import WriteFragmentOptions = DataProxy.WriteFragmentOptions;
+ export import UpdateQueryOptions = DataProxy.UpdateQueryOptions;
+ export import UpdateFragmentOptions = DataProxy.UpdateFragmentOptions;
export import Fragment = DataProxy.Fragment;
}
diff --git a/src/cache/core/types/DataProxy.ts b/src/cache/core/types/DataProxy.ts
index 8e04d01a5df..1793149688c 100644
--- a/src/cache/core/types/DataProxy.ts
+++ b/src/cache/core/types/DataProxy.ts
@@ -118,6 +118,18 @@ export namespace DataProxy {
export interface WriteFragmentOptions
extends Fragment, WriteOptions {}
+ export interface UpdateQueryOptions
+ extends Omit<(
+ ReadQueryOptions &
+ WriteQueryOptions
+ ), 'data'> {}
+
+ export interface UpdateFragmentOptions
+ extends Omit<(
+ ReadFragmentOptions &
+ WriteFragmentOptions
+ ), 'data'> {}
+
export type DiffResult = {
result?: T;
complete?: boolean;