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

feat(@apollo/react-hooks): implement skip option for useSubscription hook #3356

Merged
merged 9 commits into from Aug 23, 2019
2 changes: 2 additions & 0 deletions Changelog.md
Expand Up @@ -6,6 +6,8 @@

- Change the default query `data` state from `{}` to `undefined`. This change aligns all parts of the React Apollo query cycle so that `data` is always `undefined` if there is no data, instead of `data` being converted into an empty object. This change impacts the initial query response, initial SSR response, `data` value when errors occur, `data` value when skipping, etc. All of these areas are now aligned to only ever return a value for `data` if there really is a value to return (instead of making it seem like there is one by converting to `{}`). <br/>
[@hwillson](https://github.com/hwillson) in [#3388](https://github.com/apollographql/react-apollo/pull/3388)
- Adds support for the `skip` option when using `useSubscription`. <br/>
[@n1ru4l](https://github.com/n1ru4l) in [#3356](https://github.com/apollographql/react-apollo/pull/3356)
- Documentation fixes. <br/>
[@SeanRoberts](https://github.com/SeanRoberts) in [#3380](https://github.com/apollographql/react-apollo/pull/3380)

Expand Down
1 change: 1 addition & 0 deletions packages/common/src/types/types.ts
Expand Up @@ -161,6 +161,7 @@ export interface BaseSubscriptionOptions<
| boolean
| ((options: BaseSubscriptionOptions<TData, TVariables>) => boolean);
client?: ApolloClient<object>;
skip?: boolean;
onSubscriptionData?: (options: OnSubscriptionDataOptions<TData>) => any;
onSubscriptionComplete?: () => void;
}
Expand Down
148 changes: 148 additions & 0 deletions packages/hooks/src/__tests__/useSubscription.test.tsx
Expand Up @@ -141,4 +141,152 @@ describe('useSubscription Hook', () => {
</ApolloProvider>
).unmount;
});

it('should never execute a subscription with the skip option', done => {
const subscription = gql`
subscription {
car {
make
}
}
`;

const link = new MockSubscriptionLink();
const client = new ApolloClient({
link,
cache: new Cache({ addTypename: false })
});

let renderCount = 0;
let onSubscriptionDataCount = 0;
let unmount: any;

const Component = () => {
const { loading, data, error } = useSubscription(subscription, {
skip: true,
onSubscriptionData() {
onSubscriptionDataCount += 1;
}
});
switch (renderCount) {
case 0:
expect(loading).toBe(false);
expect(error).toBeUndefined();
expect(data).toBeUndefined();
setTimeout(() => {
unmount();
setTimeout(() => {
expect(onSubscriptionDataCount).toEqual(0);
expect(renderCount).toEqual(1);
done();
});
});
break;
default:
}
renderCount += 1;
return null;
};

unmount = render(
<ApolloProvider client={client}>
<Component />
</ApolloProvider>
).unmount;
});

it('should create a subscription after skip has changed from true to a falsy value', done => {
const subscription = gql`
subscription {
car {
make
}
}
`;

const results = [
{
result: { data: { car: { make: 'Pagani' } } }
},
{
result: { data: { car: { make: 'Scoop' } } }
}
];

const link = new MockSubscriptionLink();
const client = new ApolloClient({
link,
cache: new Cache({ addTypename: false })
});

let renderCount = 0;
let unmount: any;

const Component = () => {
const [, triggerRerender] = React.useState(0);
const [skip, setSkip] = React.useState(true);
const { loading, data, error } = useSubscription(subscription, {
skip
});
switch (renderCount) {
case 0:
expect(loading).toBe(false);
expect(error).toBeUndefined();
expect(data).toBeUndefined();
setSkip(false);
break;
case 1:
expect(loading).toBe(true);
expect(error).toBeUndefined();
expect(data).toBeUndefined();
link.simulateResult(results[0]);
break;
case 2:
expect(loading).toBe(false);
expect(data).toEqual(results[0].result.data);
setSkip(true);
break;
case 3:
expect(loading).toBe(false);
expect(data).toBeUndefined();
expect(error).toBeUndefined();
// ensure state persists across rerenders
triggerRerender(i => i + 1);
break;
case 4:
expect(loading).toBe(false);
expect(data).toBeUndefined();
expect(error).toBeUndefined();
setSkip(false);
break;
case 5:
expect(loading).toBe(true);
expect(error).toBeUndefined();
expect(data).toBeUndefined();
link.simulateResult(results[1]);
break;
case 6:
expect(loading).toBe(false);
expect(error).toBeUndefined();
expect(data).toEqual(results[1].result.data);
setTimeout(() => {
unmount();
setTimeout(() => {
expect(renderCount).toEqual(7);
done();
});
});
break;
default:
}
renderCount += 1;
return null;
};

unmount = render(
<ApolloProvider client={client}>
<Component />
</ApolloProvider>
).unmount;
});
});
19 changes: 14 additions & 5 deletions packages/hooks/src/data/SubscriptionData.ts
Expand Up @@ -26,8 +26,17 @@ export class SubscriptionData<
}

public execute(result: SubscriptionResult<TData>) {
let currentResult = result;
if (this.getOptions().skip === true) {
this.cleanup();
return {
loading: false,
error: undefined,
data: undefined,
variables: this.getOptions().variables
};
}

let currentResult = result;
if (this.refreshClient().isNew) {
currentResult = this.getLoadingResult();
}
Expand All @@ -42,10 +51,10 @@ export class SubscriptionData<
this.previousOptions &&
Object.keys(this.previousOptions).length > 0 &&
(this.previousOptions.subscription !== this.getOptions().subscription ||
!isEqual(this.previousOptions.variables, this.getOptions().variables))
!isEqual(this.previousOptions.variables, this.getOptions().variables) ||
this.previousOptions.skip !== this.getOptions().skip)
) {
this.endSubscription();
delete this.currentObservable.query;
this.cleanup();
currentResult = this.getLoadingResult();
}

Expand All @@ -66,7 +75,7 @@ export class SubscriptionData<
}

private initialize(options: SubscriptionOptions<TData, TVariables>) {
if (this.currentObservable.query) return;
if (this.currentObservable.query || this.getOptions().skip === true) return;
this.currentObservable.query = this.refreshClient().client.subscribe({
query: options.subscription,
variables: options.variables,
Expand Down
8 changes: 4 additions & 4 deletions packages/hooks/src/useSubscription.ts
Expand Up @@ -10,14 +10,14 @@ export function useSubscription<TData = any, TVariables = OperationVariables>(
options?: SubscriptionHookOptions<TData, TVariables>
) {
const context = useContext(getApolloContext());
const updatedOptions = options
? { ...options, subscription }
: { subscription };
const [result, setResult] = useState({
loading: true,
loading: !updatedOptions.skip,
error: undefined,
data: undefined
});
const updatedOptions = options
? { ...options, subscription }
: { subscription };

const subscriptionDataRef = useRef<SubscriptionData<TData, TVariables>>();
function getSubscriptionDataRef() {
Expand Down