Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(core) - Improve Suspense implementation and fix client-side Suspense #1123

Merged
merged 11 commits into from
Nov 6, 2020
7 changes: 7 additions & 0 deletions .changeset/strange-beans-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@urql/core': minor
'@urql/preact': minor
'urql': minor
---

Improve the Suspense implementation, which fixes edge-cases when Suspense is used with subscriptions, partially disabled, or _used on the client-side_. It has now been ensured that client-side suspense functions without the deprecated `suspenseExchange` and uncached results are loaded consistently. As part of this work, the `Client` itself does now never throw Suspense promises anymore, which is functionality that either way has no place outside of the React/Preact bindings.
12 changes: 5 additions & 7 deletions exchanges/suspense/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ suspense on the server.
But since `<Suspense>` is mainly intended for client-side use it made sense to build and publish
this exchange, which allows you to try out `urql` and suspense in your React app!

> ⚠️ **\*Deprecated**:
> This package is deprecated! Usage of client-side suspense with `urql` isn't recommended anymore
> and this packages has been marked as _deprecated_ after being _experimental_, since all it allows
> for is to use Suspense as a fancier loading boundary, which isn't its intended use.
> This exchange may still be useful when used with care, but it's worth keeping in mind that the
> suspense patterns in `urql` for the client-side may change.
> Suspense-mode usage for SSR remains unchanged and undeprecated however.
## ⚠️ Deprecated

Starting from `[email protected]` / `@urql/[email protected]` this exchange isn't required anymore to enable
client-side suspense support. Instead the `useQuery` hook internally handles React Suspense and
caches a result between Suspense and the subsequent re-mount of your component automatically.

## Quick Start Guide

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ describe('promisified methods', () => {
requestPolicy: 'cache-and-network',
fetchOptions: undefined,
fetch: undefined,
suspense: false,
preferGetMethod: false,
});
expect(mutationResult).toHaveProperty('then');
Expand Down
56 changes: 27 additions & 29 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {

import {
createRequest,
toSuspenseSource,
withPromise,
maskTypename,
noop,
Expand Down Expand Up @@ -173,14 +172,19 @@ export class Client {

createOperationContext = (
opts?: Partial<OperationContext>
): OperationContext => ({
url: this.url,
fetchOptions: this.fetchOptions,
fetch: this.fetch,
preferGetMethod: this.preferGetMethod,
...opts,
requestPolicy: (opts || {}).requestPolicy || this.requestPolicy,
});
): OperationContext => {
if (!opts) opts = {};

return {
url: this.url,
fetchOptions: this.fetchOptions,
fetch: this.fetch,
preferGetMethod: this.preferGetMethod,
...opts,
suspense: opts.suspense || (opts.suspense !== false && this.suspense),
requestPolicy: opts.requestPolicy || this.requestPolicy,
};
};

createRequestOperation = <Data = any, Variables = object>(
kind: OperationType,
Expand Down Expand Up @@ -222,10 +226,9 @@ export class Client {
executeRequestOperation<Data = any, Variables = object>(
operation: Operation<Data, Variables>
): Source<OperationResult<Data, Variables>> {
const { key, kind } = operation;
let operationResults$ = pipe(
this.results$,
filter((res: OperationResult) => res.operation.key === key)
filter((res: OperationResult) => res.operation.key === operation.key)
) as Source<OperationResult<Data, Variables>>;

if (this.maskTypename) {
Expand All @@ -238,7 +241,7 @@ export class Client {
);
}

if (kind === 'mutation') {
if (operation.kind === 'mutation') {
// A mutation is always limited to just a single result and is never shared
return pipe(
operationResults$,
Expand All @@ -249,7 +252,9 @@ export class Client {

const teardown$ = pipe(
this.operations$,
filter((op: Operation) => op.kind === 'teardown' && op.key === key)
filter(
(op: Operation) => op.kind === 'teardown' && op.key === operation.key
)
);

const result$ = pipe(
Expand All @@ -263,11 +268,14 @@ export class Client {
})
);

return operation.context.suspense !== false &&
this.suspense &&
kind === 'query'
? toSuspenseSource<OperationResult>(result$ as Source<OperationResult>)
: (result$ as Source<OperationResult>);
if (operation.kind === 'query' && operation.context.pollInterval) {
return pipe(
merge([fromValue(0), interval(operation.context.pollInterval)]),
switchMap(() => result$)
);
}

return result$;
}

query<Data = any, Variables extends object = {}>(
Expand Down Expand Up @@ -309,17 +317,7 @@ export class Client {
opts?: Partial<OperationContext>
): Source<OperationResult<Data, Variables>> => {
const operation = this.createRequestOperation('query', query, opts);
const response$ = this.executeRequestOperation<Data, Variables>(operation);
const { pollInterval } = operation.context;

if (pollInterval) {
return pipe(
merge([fromValue(0), interval(pollInterval)]),
switchMap(() => response$)
);
}

return response$;
return this.executeRequestOperation<Data, Variables>(operation);
};

subscription<Data = any, Variables extends object = {}>(
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export * from './error';
export * from './request';
export * from './result';
export * from './typenames';
export * from './toSuspenseSource';
export * from './stringifyVariables';
export * from './maskTypename';
export * from './withPromise';
Expand Down
111 changes: 0 additions & 111 deletions packages/core/src/utils/toSuspenseSource.test.ts

This file was deleted.

20 changes: 0 additions & 20 deletions packages/core/src/utils/toSuspenseSource.ts

This file was deleted.

Loading