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

ssr false queries still being called on server #3515

Merged
merged 13 commits into from Oct 1, 2019
1 change: 1 addition & 0 deletions examples/ssr/hooks/package.json
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"@apollo/react-hooks": "3.1.1",
"@apollo/react-ssr": "^3.1.1",
"@babel/runtime": "^7.4.5",
"apollo-cache-inmemory": "^1.6.0",
"apollo-client": "^2.6.0",
Expand Down
3 changes: 2 additions & 1 deletion examples/ssr/hooks/server/main.js
@@ -1,7 +1,8 @@
import { renderToString } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { ApolloClient } from 'apollo-client';
import { getMarkupFromTree, ApolloProvider } from '@apollo/react-hooks';
import { ApolloProvider } from '@apollo/react-hooks';
import { getMarkupFromTree } from '@apollo/react-ssr';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { WebApp } from 'meteor/webapp';
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/__tests__/useLazyQuery.test.tsx
Expand Up @@ -415,7 +415,7 @@ describe('useLazyQuery Hook', () => {
expect(loading).toEqual(false);
expect(data).toEqual(CAR_RESULT_DATA);
setTimeout(() => {
execute();
execute({ variables: { someProp: 'someValue' } });
});
break;
case 3:
Expand Down
7 changes: 5 additions & 2 deletions packages/hooks/src/data/OperationData.ts
Expand Up @@ -30,8 +30,11 @@ export abstract class OperationData<TOptions = any> {
return this.options;
}

public setOptions(newOptions: CommonOptions<TOptions>) {
if (!isEqual(this.options, newOptions)) {
public setOptions(
newOptions: CommonOptions<TOptions>,
storePrevious: boolean = false
) {
if (storePrevious && !isEqual(this.options, newOptions)) {
this.previousOptions = this.options;
}
this.options = newOptions;
Expand Down
88 changes: 44 additions & 44 deletions packages/hooks/src/data/QueryData.ts
Expand Up @@ -48,7 +48,7 @@ export class QueryData<TData, TVariables> extends OperationData {
public execute(): QueryResult<TData, TVariables> {
this.refreshClient();

const { skip, query, ssr } = this.getOptions();
const { skip, query } = this.getOptions();
if (skip || query !== this.previousData.query) {
this.removeQuerySubscription();
this.previousData.query = query;
Expand All @@ -58,9 +58,7 @@ export class QueryData<TData, TVariables> extends OperationData {

if (this.isMounted) this.startQuerySubscription();

const ssrDisabled = ssr === false;

return this.getExecuteSsrResult(ssrDisabled) || this.getExecuteResult();
return this.getExecuteSsrResult() || this.getExecuteResult();
}

public executeLazy(): QueryTuple<TData, TVariables> {
Expand Down Expand Up @@ -90,6 +88,7 @@ export class QueryData<TData, TVariables> extends OperationData {

public afterExecute({ lazy = false }: { lazy?: boolean } = {}) {
this.isMounted = true;

if (!lazy || this.runLazy) {
this.handleErrorOrCompleted();

Expand All @@ -103,6 +102,7 @@ export class QueryData<TData, TVariables> extends OperationData {
});
}

this.previousOptions = this.getOptions();
return this.unmount.bind(this);
}

Expand All @@ -114,25 +114,24 @@ export class QueryData<TData, TVariables> extends OperationData {

public getOptions() {
const options = super.getOptions();
const lazyOptions = this.lazyOptions || {};
const updatedOptions = {
...options,
variables: {

if (this.lazyOptions) {
options.variables = {
...options.variables,
...lazyOptions.variables
},
context: {
...this.lazyOptions.variables
};
options.context = {
...options.context,
...lazyOptions.context
}
};
...this.lazyOptions.context
};
}

// skip is not supported when using lazy query execution.
if (this.runLazy) {
delete updatedOptions.skip;
delete options.skip;
}

return updatedOptions;
return options;
}

private runLazyQuery = (options?: QueryLazyOptions<TVariables>) => {
Expand All @@ -149,29 +148,31 @@ export class QueryData<TData, TVariables> extends OperationData {
return result;
};

private getExecuteSsrResult(ssrDisabled: boolean) {
let result;

if (this.context && this.context.renderPromises) {
const ssrLoading = {
loading: true,
networkStatus: NetworkStatus.loading,
called: true,
data: undefined
};

// SSR is disabled, so just return the loading event and leave it in that state.
if (ssrDisabled) {
return ssrLoading;
}
private getExecuteSsrResult() {
const treeRenderingInitiated = this.context && this.context.renderPromises;
const ssrDisabled = this.getOptions().ssr === false;
const fetchDisabled = this.refreshClient().client.disableNetworkFetches;

const ssrLoading = {
loading: true,
networkStatus: NetworkStatus.loading,
called: true,
data: undefined
} as QueryResult<TData, TVariables>;

// If SSR has been explicitly disabled, and this function has been called
// on the server side, return the default loading state.
if (ssrDisabled && (treeRenderingInitiated || fetchDisabled)) {
return ssrLoading;
}

result = this.context.renderPromises.addQueryPromise(
this,
this.getExecuteResult
);
if (!result) {
result = ssrLoading as QueryResult<TData, TVariables>;
}
let result;
if (treeRenderingInitiated) {
result =
this.context.renderPromises!.addQueryPromise(
this,
this.getExecuteResult
) || ssrLoading;
}

return result;
Expand All @@ -196,7 +197,7 @@ export class QueryData<TData, TVariables> extends OperationData {
return {
...options,
displayName,
context: options.context || {},
context: options.context,
metadata: { reactComponent: { displayName } }
};
}
Expand All @@ -213,13 +214,14 @@ export class QueryData<TData, TVariables> extends OperationData {

if (!this.currentObservable.query) {
const observableQueryOptions = this.prepareObservableQueryOptions();

this.previousData.observableQueryOptions = {
...observableQueryOptions,
children: null
};
this.currentObservable.query = this.refreshClient().client.watchQuery(
observableQueryOptions
);
this.currentObservable.query = this.refreshClient().client.watchQuery({
...observableQueryOptions
});

if (this.context && this.context.renderPromises) {
this.context.renderPromises.registerSSRObservable(
Expand Down Expand Up @@ -266,7 +268,6 @@ export class QueryData<TData, TVariables> extends OperationData {
this.currentObservable.subscription = obsQuery.subscribe({
next: ({ loading, networkStatus, data }) => {
const previousResult = this.previousData.result;

if (previousResult) {
// Calls to `ObservableQuery.fetchMore` return a result before the
// `updateQuery` function fully finishes. This can lead to an
Expand All @@ -282,7 +283,6 @@ export class QueryData<TData, TVariables> extends OperationData {
) {
return;
}

// Make sure we're not attempting to re-render similar results
if (
previousResult.loading === loading &&
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/useSubscription.ts
Expand Up @@ -32,7 +32,7 @@ export function useSubscription<TData = any, TVariables = OperationVariables>(
}

const subscriptionData = getSubscriptionDataRef();
subscriptionData.setOptions(updatedOptions);
subscriptionData.setOptions(updatedOptions, true);
subscriptionData.context = context;

useEffect(() => subscriptionData.afterExecute());
Expand Down
68 changes: 65 additions & 3 deletions packages/ssr/src/__tests__/useQuery.test.tsx
@@ -1,9 +1,13 @@
import React from 'react';
import { DocumentNode } from 'graphql';
import gql from 'graphql-tag';
import { MockedProvider } from '@apollo/react-testing';
import { ApolloProvider } from '@apollo/react-common';
import { MockedProvider, mockSingleLink } from '@apollo/react-testing';
import { useQuery } from '@apollo/react-hooks';
import { renderToStringWithData } from '@apollo/react-ssr';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { render, wait } from '@testing-library/react';

describe('useQuery Hook SSR', () => {
const CAR_QUERY: DocumentNode = gql`
Expand Down Expand Up @@ -80,13 +84,13 @@ describe('useQuery Hook SSR', () => {
return renderToStringWithData(app);
});

it('should skip SSR if `ssr` option is `false`', async () => {
it('should skip SSR tree rendering if `ssr` option is `false`', async () => {
let renderCount = 0;
const Component = () => {
const { data, loading } = useQuery(CAR_QUERY, { ssr: false });
renderCount += 1;

if (!loading) {
expect(data).toEqual(CAR_RESULT_DATA);
const { make } = data.cars[0];
return <div>{make}</div>;
}
Expand All @@ -104,4 +108,62 @@ describe('useQuery Hook SSR', () => {
expect(result).toEqual('');
});
});

it(
'should skip both SSR tree rendering and SSR component rendering if ' +
'`ssr` option is `false` and `ssrMode` is `true`',
async () => {
const link = mockSingleLink({
request: { query: CAR_QUERY },
result: { data: CAR_RESULT_DATA }
});

const client = new ApolloClient({
cache: new InMemoryCache(),
link,
ssrMode: true
});

let renderCount = 0;
const Component = () => {
const { data, loading } = useQuery(CAR_QUERY, { ssr: false });

let content = null;
switch (renderCount) {
case 0:
expect(loading).toBeTruthy();
expect(data).toBeUndefined();
break;
case 1: // FAIL; should not render a second time
default:
}

renderCount += 1;
return content;
};

const app = (
<ApolloProvider client={client}>
<Component />
</ApolloProvider>
);

await renderToStringWithData(app).then(result => {
expect(renderCount).toBe(1);
expect(result).toEqual('');
});

renderCount = 0;

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

await wait(() => {
expect(renderCount).toBe(1);
});
}
);
});