Skip to content

Commit

Permalink
Add onResetStore method to client to register callbacks after resetSt…
Browse files Browse the repository at this point in the history
…ore (#2812)

* Added onResetStore method to client

* Add test for onResetStore

* Fix typings

* Added PR to changelog

* Added another test with multiple callbacks to onResetStore

* Added docs for resetStore & onResetStore

* Add & document unsubscribe method

* Fix test, bump up bundlesize

Bump up bundlesize
  • Loading branch information
peggyrayzis authored and James Baxley committed Jan 5, 2018
1 parent bae1145 commit f25aa16
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 15 deletions.
62 changes: 62 additions & 0 deletions docs/source/features/cache-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,3 +475,65 @@ cacheResolvers: {
},
},
```

<h2 id="reset-store">Resetting the store</h2>

Sometimes, you may want to reset the store entirely, such as [when a user logs out](../recipes/authentication.html#login-logout). To accomplish this, use `client.resetStore` to clear out your Apollo cache. Since `client.resetStore` also refetches any of your active queries for you, it is asynchronous.

```js
export default withApollo(graphql(PROFILE_QUERY, {
props: ({ data: { loading, currentUser }, client }) => ({
loading,
currentUser,
resetOnLogout: async () => client.resetStore(),
}),
})(Profile));
```

To register a callback function to be executed after the store has been reset, call `client.onResetStore` and pass in your callback. If you would like to register multiple callbacks, simply call `client.onResetStore` again. All of your callbacks will be pushed into an array and executed concurrently.

In this example, we're using `client.onResetStore` to write our default values to the cache for [`apollo-link-state`](docs/link/links/state). This is necessary if you're using `apollo-link-state` for local state management and calling `client.resetStore` anywhere in your application.

```js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { withClientState } from 'apollo-link-state';

import { resolvers, defaults } from './resolvers';

const cache = new InMemoryCache();
const stateLink = withClientState({ cache, resolvers, defaults });

const client = new ApolloClient({
cache,
link: stateLink,
});

client.onResetStore(stateLink.writeDefaults);
```

You can also call `client.onResetStore` from your React components. This can be useful if you would like to force your UI to rerender after the store has been reset.

If you would like to unsubscribe your callbacks from resetStore, use the return value of `client.onResetStore` for your unsubscribe function.

```js
import { withApollo } from "react-apollo";

export class Foo extends Component {
constructor(props) {
super(props);
this.unsubscribe = props.client.onResetStore(
() => this.setState({ reset: false }
);
this.state = { reset: false };
}
componentDidUnmount() {
this.unsubscribe();
}
render() {
return this.state.reset ? <div /> : <span />
}
}

export default withApollo(Foo);
```
1 change: 1 addition & 0 deletions docs/source/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The `ApolloClient` class is the core API for Apollo, and the one you'll need to
{% tsapibox ApolloClient.writeQuery %}
{% tsapibox ApolloClient.writeFragment %}
{% tsapibox ApolloClient.resetStore %}
{% tsapibox ApolloClient.onResetStore %}

<h2 id="ObservableQuery">ObservableQuery</h2>

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
{
"name": "apollo-client",
"path": "./packages/apollo-client/lib/bundle.min.js",
"maxSize": "11.6 kB"
"maxSize": "11.7 kB"
},
{
"name": "apollo-client-preset",
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

### vNEXT
- include `optimisticResponse` in the context passed to apollo-link for mutations [PR#2704](https://github.com/apollographql/apollo-client/pull/2704)
- add `onResetStore` method to the client to register callbacks after `client.resetStore` is called [PR#2812](https://github.com/apollographql/apollo-client/pull/2812)

### 2.1.1
- fix eslint usage by fixing jsnext:main path
Expand Down
38 changes: 24 additions & 14 deletions packages/apollo-client/src/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
private devToolsHookCb: Function;
private proxy: ApolloCache<TCacheShape> | undefined;
private ssrMode: boolean;
private resetStoreCallbacks: Array<() => Promise<any>> = [];

/**
* Constructs an instance of {@link ApolloClient}.
Expand Down Expand Up @@ -206,10 +207,7 @@ export default class ApolloClient<TCacheShape> implements DataProxy {

// XXX Overwriting options is probably not the best way to do this long term...
if (this.disableNetworkFetches && options.fetchPolicy === 'network-only') {
options = {
...options,
fetchPolicy: 'cache-first',
} as WatchQueryOptions;
options = { ...options, fetchPolicy: 'cache-first' } as WatchQueryOptions;
}

return this.queryManager.watchQuery<T>(options);
Expand Down Expand Up @@ -239,10 +237,7 @@ export default class ApolloClient<TCacheShape> implements DataProxy {

// XXX Overwriting options is probably not the best way to do this long term...
if (this.disableNetworkFetches && options.fetchPolicy === 'network-only') {
options = {
...options,
fetchPolicy: 'cache-first',
} as WatchQueryOptions;
options = { ...options, fetchPolicy: 'cache-first' } as WatchQueryOptions;
}

return this.queryManager.query<T>(options);
Expand Down Expand Up @@ -379,9 +374,25 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
* active queries.
*/
public resetStore(): Promise<ApolloQueryResult<any>[]> | Promise<null> {
return this.queryManager
? this.queryManager.resetStore()
: Promise.resolve(null);
return Promise.resolve()
.then(() => {
this.queryManager
? this.queryManager.resetStore()
: Promise.resolve(null);
})
.then(() => Promise.all(this.resetStoreCallbacks.map(fn => fn())));
}

/**
* Allows callbacks to be registered that are executed with the store is reset.
* onResetStore returns an unsubscribe function for removing your registered callbacks.
*/

public onResetStore(cb: () => Promise<any>): () => void {
this.resetStoreCallbacks.push(cb);
return () => {
this.resetStoreCallbacks = this.resetStoreCallbacks.filter(c => c !== cb);
};
}

/**
Expand All @@ -401,8 +412,8 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
return this.queryManager
? this.queryManager.reFetchObservableQueries()
: Promise.resolve(null);
}
}

/**
* Exposes the cache's complete state, in a serializable format for later restoration.
*/
Expand All @@ -419,7 +430,6 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
*/
public restore(serializedState: TCacheShape): ApolloCache<TCacheShape> {
return this.initProxy().restore(serializedState);

}

/**
Expand Down
58 changes: 58 additions & 0 deletions packages/apollo-client/src/__tests__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,64 @@ describe('client', () => {
client.resetStore();
});

it('has an onResetStore method which takes a callback to be called after resetStore', async () => {
const client = new ApolloClient({
link: ApolloLink.empty(),
cache: new InMemoryCache(),
});

const onResetStore = jest.fn();
client.onResetStore(onResetStore);

await client.resetStore();

expect(onResetStore).toHaveBeenCalled();
});

it('onResetStore returns a method that unsubscribes the callback', async () => {
const client = new ApolloClient({
link: ApolloLink.empty(),
cache: new InMemoryCache(),
});

const onResetStore = jest.fn();
const unsubscribe = client.onResetStore(onResetStore);

unsubscribe();

await client.resetStore();
expect(onResetStore).not.toHaveBeenCalled();
});

it('resetStore waits until all onResetStore callbacks are called', async () => {
const delay = time => new Promise(r => setTimeout(r, time));

const client = new ApolloClient({
link: ApolloLink.empty(),
cache: new InMemoryCache(),
});

let count = 0;
const onResetStoreOne = jest.fn(async () => {
expect(count).toEqual(0);
await delay(10).then(() => count++);
expect(count).toEqual(1);
});

const onResetStoreTwo = jest.fn(async () => {
expect(count).toEqual(0);
await delay(11).then(() => count++);
expect(count).toEqual(2);
});

client.onResetStore(onResetStoreOne);
client.onResetStore(onResetStoreTwo);

expect(count).toEqual(0);
await client.resetStore();
expect(count).toEqual(2);
});

it('has a reFetchObservableQueries method which calls QueryManager', done => {
const client = new ApolloClient({
link: ApolloLink.empty(),
Expand Down

0 comments on commit f25aa16

Please sign in to comment.