diff --git a/Changelog.md b/Changelog.md index e4a66e9274..ff7619becd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,8 @@ Expect active development and potentially significant breaking changes in the `0 [PR #391](https://github.com/apollostack/react-apollo/pull/391) gets rid of warnings during redux test. + [PR #389](https://github.com/apollostack/react-apollo/pull/389) added a shouldResubscribe option to allow subscriptions to automatically resubscribe when props change. + ### v0.7.2 - Bug: fix issue where changing variables while unskipping didn't result in the variables actually changing - [Issue #374](https://github.com/apollostack/react-apollo/issues/374) diff --git a/src/graphql.tsx b/src/graphql.tsx index 4fca04092d..6d9e34c586 100644 --- a/src/graphql.tsx +++ b/src/graphql.tsx @@ -118,6 +118,7 @@ export interface OperationOption { skip?: boolean | ((props: any) => boolean); name?: string; withRef?: boolean; + shouldResubscribe?: (props: any, nextProps: any) => boolean; } export default function graphql( @@ -212,7 +213,15 @@ export default function graphql( if (this.type === DocumentType.Mutation) { return; }; - + if (this.type === DocumentType.Subscription + && operationOptions.shouldResubscribe + && operationOptions.shouldResubscribe(this.props, nextProps)) { + this.unsubscribeFromQuery(); + delete this.queryObservable; + this.updateQuery(nextProps); + this.subscribeToQuery(); + return; + } if (this.shouldSkip(nextProps)) { if (!this.shouldSkip(this.props)) { // if this has changed, we better unsubscribe @@ -328,7 +337,9 @@ export default function graphql( // This workaround is only present in Apollo Client 0.4.21 this.queryObservable._setOptionsNoResult(opts); } else { - this.queryObservable.setOptions(opts); + if (this.queryObservable.setOptions) { + this.queryObservable.setOptions(opts); + } } } @@ -475,7 +486,6 @@ export default function graphql( } if (operationOptions.withRef) mergedPropsAndData.ref = 'wrappedInstance'; - this.renderedElement = createElement(WrappedComponent, mergedPropsAndData); return this.renderedElement; } diff --git a/test/react-web/client/graphql/subscriptions.test.tsx b/test/react-web/client/graphql/subscriptions.test.tsx index 947e5de977..329a71fe1b 100644 --- a/test/react-web/client/graphql/subscriptions.test.tsx +++ b/test/react-web/client/graphql/subscriptions.test.tsx @@ -10,6 +10,8 @@ declare function require(name: string); import { mockSubscriptionNetworkInterface } from '../../../../src/test-utils'; import { ApolloProvider, graphql } from '../../../../src'; + + describe('subscriptions', () => { const results = ['James Baxley', 'John Pinkerton', 'Sam Clairidge', 'Ben Coleman'].map( name => ({ result: { user: { name } }, delay: 10 }) @@ -124,5 +126,101 @@ describe('subscriptions', () => { ); }); +it('resubscribes to a subscription', (done) => { + //we make an extra Hoc which will trigger the inner HoC to resubscribe + //these are the results for the outer subscription + const triggerResults = ['0', 'trigger resubscribe', '3', '4', '5', '6', '7'].map( + trigger => ({ result: { trigger }, delay: 10 }) + ); + //These are the results fro the resubscription + const results3 = ['NewUser: 1', 'NewUser: 2', 'NewUser: 3', 'NewUser: 4'].map( + name => ({ result: { user: { name } }, delay: 10 }) + ); + + + const query = gql`subscription UserInfo { user { name } }`; + const triggerQuery = gql`subscription Trigger { trigger }`; + const networkInterface = mockSubscriptionNetworkInterface( + [ + { request: { query }, results: [...results] }, + { request: { query: triggerQuery }, results: [...triggerResults] }, + { request: { query }, results: [...results3] }, + ] + ); + const client = new ApolloClient({ networkInterface, addTypename: false }); + // XXX fix in apollo-client + client.subscribe = client.subscribe.bind(client); + + let count = 0; + let unsubscribed = false; + let output; + @graphql(triggerQuery) + @graphql(query, { + shouldResubscribe: (props, nextProps) => { + return nextProps.data.trigger === 'trigger resubscribe'; + } + }) + class Container extends React.Component { + componentWillMount(){ + expect(this.props.data.loading).toBeTruthy(); + } + componentWillReceiveProps({ data: { loading, user }}) { + // odd counts will be outer wrapper getting subscriptions - ie unchanged + expect(loading).toBeFalsy(); + if (count === 0) expect(user).toEqual(results[0].result.user); + if (count === 1) expect(user).toEqual(results[0].result.user); + if (count === 2) expect(user).toEqual(results[1].result.user); + if (count === 3) expect(user).toEqual(results[1].result.user); + if (count <= 1) { + expect(networkInterface.mockedSubscriptionsById[0]).toBeDefined(); + } + expect(networkInterface.mockedSubscriptionsById[1]).toBeDefined(); + if (count === 2) { + expect(networkInterface.mockedSubscriptionsById[0]).toBeDefined(); + //expect(networkInterface.mockedSubscriptionsById[2]).toBeDefined(); + } + if (count === 3) { + //it's resubscribed + expect(networkInterface.mockedSubscriptionsById[0]).not.toBeDefined(); + expect(networkInterface.mockedSubscriptionsById[2]).toBeDefined(); + expect(user).toEqual(results[1].result.user); + + } + if (count === 4) { + //it's got result of new subscription + expect(user).toEqual(results3[0].result.user); + + } + if (count === 5) { + expect(user).toEqual(results3[0].result.user); + output.unmount(); + expect(networkInterface.mockedSubscriptionsById[0]).not.toBeDefined(); + expect(networkInterface.mockedSubscriptionsById[1]).not.toBeDefined(); + expect(networkInterface.mockedSubscriptionsById[3]).not.toBeDefined(); + done(); + } + + count++; + + } + render() { + return null; + } + }; + const interval = setInterval(() => { + try { + networkInterface.fireResult(count >2 ? 2 : 0 ); + networkInterface.fireResult(1); + }catch (ex) { + clearInterval(interval) + } + if (count > 3) clearInterval(interval); + }, 50); + + output = renderer.create( + + ); + + }); });