Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
Client scoped query recyclers (#513)
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Coedo committed Aug 8, 2017
1 parent 80e6f7f commit c0a6b22
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 12 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change log

### vNext
- Fix: Scope query recyclers by client [#876](https://github.com/apollographql/react-apollo/pull/876)
- Replace string refs with callback refs [#908](https://github.com/apollographql/react-apollo/pull/908)

### 1.4.10
Expand Down
7 changes: 6 additions & 1 deletion src/ApolloProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Component } from 'react';
import { Store } from 'redux';

import ApolloClient, { ApolloStore } from 'apollo-client';
import QueryRecyclerProvider from './QueryRecyclerProvider';

const invariant = require('invariant');

Expand Down Expand Up @@ -61,6 +62,10 @@ export default class ApolloProvider extends Component<ProviderProps, any> {
}

render() {
return React.Children.only(this.props.children);
return (
<QueryRecyclerProvider>
{React.Children.only(this.props.children)}
</QueryRecyclerProvider>
);
}
}
50 changes: 50 additions & 0 deletions src/QueryRecyclerProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Component, Children } from 'react';
import * as PropTypes from 'prop-types';
import { ObservableQueryRecycler } from './queryRecycler';

class QueryRecyclerProvider extends Component {
static propTypes = {
children: PropTypes.element.isRequired
};

static contextTypes = {
client: PropTypes.object
};

static childContextTypes = {
getQueryRecycler: PropTypes.func.isRequired
};

private recyclers: WeakMap<Component, ObservableQueryRecycler>;

constructor(props) {
super(props);
this.recyclers = new WeakMap();
this.getQueryRecycler = this.getQueryRecycler.bind(this);
}

componentWillReceiveProps(nextProps, nextContext) {
if (this.context.client !== nextContext.client) {
this.recyclers = new WeakMap();
}
}

getQueryRecycler(component) {
if (!this.recyclers.has(component)) {
this.recyclers.set(component, new ObservableQueryRecycler());
}
return this.recyclers.get(component);
}

getChildContext() {
return ({
getQueryRecycler: this.getQueryRecycler
});
}

render() {
return Children.only(this.props.children);
}
}

export default QueryRecyclerProvider;
20 changes: 10 additions & 10 deletions src/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,12 @@ export default function graphql<
function wrapWithApolloComponent(WrappedComponent) {
const graphQLDisplayName = `${alias}(${getDisplayName(WrappedComponent)})`;

// A recycler that we can use to recycle old observable queries to keep
// them hot between component unmounts and remounts.
//
// Note that the existence of this recycler could potentially cause memory
// leaks if many components are being created and unmounted in parallel.
// However, this is an unlikely scenario.
const recycler = new ObservableQueryRecycler();

class GraphQL extends Component<TProps, void> {
static displayName = graphQLDisplayName;
static WrappedComponent = WrappedComponent;
static contextTypes = {
client: PropTypes.object,
getQueryRecycler: PropTypes.func,
};

// react / redux and react dev tools (HMR) needs
Expand All @@ -121,6 +114,7 @@ export default function graphql<
// data storage
private store: ApolloStore;
private client: ApolloClient; // apollo client
private recycler: ObservableQueryRecycler;
private type: DocumentType;

// request / action storage. Note that we delete querySubscription if we
Expand All @@ -142,6 +136,7 @@ export default function graphql<

constructor(props, context) {
super(props, context);

this.version = version;
this.type = operation.type;
this.dataForChildViaMutation = this.dataForChildViaMutation.bind(this);
Expand Down Expand Up @@ -183,6 +178,7 @@ export default function graphql<
this.unsubscribeFromQuery();
this.queryObservable = null;
this.previousData = {};

this.updateQuery(nextProps);
if (!this.shouldSkip(nextProps)) {
this.subscribeToQuery();
Expand Down Expand Up @@ -219,7 +215,7 @@ export default function graphql<
if (this.type === DocumentType.Query) {
// Recycle the query observable if there ever was one.
if (this.queryObservable) {
recycler.recycle(this.queryObservable);
this.getQueryRecycler().recycle(this.queryObservable);
delete this.queryObservable;
}

Expand All @@ -237,6 +233,10 @@ export default function graphql<
this.hasMounted = false;
}

getQueryRecycler() {
return this.context.getQueryRecycler(GraphQL);
}

getClient(): ApolloClient {
if (this.client) return this.client;
const { client } = mapPropsToOptions(this.props);
Expand Down Expand Up @@ -337,7 +337,7 @@ export default function graphql<
// Try to reuse an `ObservableQuery` instance from our recycler. If
// we get null then there is no instance to reuse and we should
// create a new `ObservableQuery`. Otherwise we will use our old one.
const queryObservable = recycler.reuse(opts);
const queryObservable = this.getQueryRecycler().reuse(opts);

if (queryObservable === null) {
this.queryObservable = this.getClient().watchQuery(
Expand Down
3 changes: 2 additions & 1 deletion test/react-web/client/graphql/client-option.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import gql from 'graphql-tag';
import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
import { mockNetworkInterface } from '../../../../src/test-utils';
import { ApolloProvider, graphql } from '../../../../src';
import { ObservableQueryRecycler } from '../../../../src/queryRecycler';

describe('client option', () => {
it('renders with client from options', () => {
Expand All @@ -33,7 +34,7 @@ describe('client option', () => {
},
};
const ContainerWithData = graphql(query, config)(props => null);
shallow(<ContainerWithData />);
shallow(<ContainerWithData />, { context: { getQueryRecycler: () => new ObservableQueryRecycler() }});
});

it('ignores client from context if client from options is present', done => {
Expand Down

0 comments on commit c0a6b22

Please sign in to comment.