From 4f57d506ebcbd3986402866668e19177bead78ee Mon Sep 17 00:00:00 2001 From: James Wyatt Cready-Pyle Date: Thu, 21 Mar 2019 11:34:11 -0400 Subject: [PATCH 1/5] Provide shouldInvalidatePreviousData prop Fixes #2202 --- src/Query.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Query.tsx b/src/Query.tsx index 6379f868d9..43878ce93c 100644 --- a/src/Query.tsx +++ b/src/Query.tsx @@ -94,6 +94,7 @@ export default class Query extends static propTypes = { client: PropTypes.object, children: PropTypes.func.isRequired, + shouldInvalidatePreviousData: PropTypes.func, fetchPolicy: PropTypes.string, notifyOnNetworkStatusChange: PropTypes.bool, onCompleted: PropTypes.func, @@ -202,6 +203,10 @@ export default class Query extends this.queryObservable = null; this.previousData = {}; this.updateQuery(nextProps); + } else if (typeof nextProps.shouldInvalidatePreviousData === 'function') { + if (nextProps.shouldInvalidatePreviousData(nextProps.variables, this.props.variables)) { + this.previousData = {}; + } } if (this.props.query !== nextProps.query) { From ccaddb4df9c82f1b96cdff6d854d010ad121b15c Mon Sep 17 00:00:00 2001 From: James Wyatt Cready-Pyle Date: Thu, 21 Mar 2019 11:43:18 -0400 Subject: [PATCH 2/5] Add type definition to QueryProps --- src/Query.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Query.tsx b/src/Query.tsx index 43878ce93c..3a278cc633 100644 --- a/src/Query.tsx +++ b/src/Query.tsx @@ -74,6 +74,7 @@ export interface QueryProps extend skip?: boolean; onCompleted?: (data: TData) => void; onError?: (error: ApolloError) => void; + shouldInvalidatePreviousData?: (nextVariables: TVariables, prevVariables: TVariables) => boolean; } export interface QueryContext { @@ -203,7 +204,7 @@ export default class Query extends this.queryObservable = null; this.previousData = {}; this.updateQuery(nextProps); - } else if (typeof nextProps.shouldInvalidatePreviousData === 'function') { + } else if (nextProps.shouldInvalidatePreviousData) { if (nextProps.shouldInvalidatePreviousData(nextProps.variables, this.props.variables)) { this.previousData = {}; } From 4ecd45d68d7d79f14bc5b3e58a2042ed1d6d9a21 Mon Sep 17 00:00:00 2001 From: James Wyatt Cready-Pyle Date: Thu, 21 Mar 2019 11:45:59 -0400 Subject: [PATCH 3/5] Make the variables possibly undefined --- src/Query.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Query.tsx b/src/Query.tsx index 3a278cc633..7bb6914c80 100644 --- a/src/Query.tsx +++ b/src/Query.tsx @@ -74,7 +74,7 @@ export interface QueryProps extend skip?: boolean; onCompleted?: (data: TData) => void; onError?: (error: ApolloError) => void; - shouldInvalidatePreviousData?: (nextVariables: TVariables, prevVariables: TVariables) => boolean; + shouldInvalidatePreviousData?: (nextVariables: TVariables | undefined, prevVariables: TVariables | undefined) => boolean; } export interface QueryContext { From 23269a5732d5d2a2e8a472c764a6e4c07f3c25c6 Mon Sep 17 00:00:00 2001 From: James Wyatt Cready-Pyle Date: Wed, 3 Apr 2019 08:25:55 -0400 Subject: [PATCH 4/5] Use `shallowEqual` on `variables` prop by default to invalidate `previousData` if no `shouldInvalidatePreviousData` prop was provided --- src/Query.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Query.tsx b/src/Query.tsx index cca92d547e..6bd6459c67 100644 --- a/src/Query.tsx +++ b/src/Query.tsx @@ -208,6 +208,8 @@ export default class Query extends if (nextProps.shouldInvalidatePreviousData(nextProps.variables, this.props.variables)) { this.previousData = {}; } + } else if (!shallowEqual(nextProps.variables, this.props.variables)) { + this.previousData = {}; } if (this.props.query !== nextProps.query) { From 20d044efae08df8c5be3660d72fee510563ac1f4 Mon Sep 17 00:00:00 2001 From: Wyatt Cready-Pyle Date: Wed, 3 Apr 2019 09:43:43 -0400 Subject: [PATCH 5/5] Ignore `variables.first` when doing shallow compare --- src/Query.tsx | 22 ++++++++++++++-------- src/utils/shallowEqual.ts | 33 ++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/Query.tsx b/src/Query.tsx index 6bd6459c67..d65d2fa2f4 100644 --- a/src/Query.tsx +++ b/src/Query.tsx @@ -16,7 +16,7 @@ import { getClient } from './component-utils'; import { RenderPromises } from './getDataFromTree'; import isEqual from 'lodash.isequal'; -import shallowEqual from './utils/shallowEqual'; +import shallowEqual, { shallowEqualSansFirst } from './utils/shallowEqual'; import { invariant } from 'ts-invariant'; export type ObservableQueryFields = Pick< @@ -74,7 +74,10 @@ export interface QueryProps extend skip?: boolean; onCompleted?: (data: TData) => void; onError?: (error: ApolloError) => void; - shouldInvalidatePreviousData?: (nextVariables: TVariables | undefined, prevVariables: TVariables | undefined) => boolean; + shouldInvalidatePreviousData: ( + nextVariables: TVariables | undefined, + lastVariables: TVariables | undefined, + ) => boolean; } export interface QueryContext { @@ -107,6 +110,13 @@ export default class Query extends partialRefetch: PropTypes.bool, }; + static defaultProps = { + shouldInvalidatePreviousData: ( + nextVariables: OperationVariables | undefined, + lastVariables: OperationVariables | undefined, + ) => !shallowEqualSansFirst(nextVariables, lastVariables), + }; + context: QueryContext | undefined; private client: ApolloClient; @@ -204,11 +214,7 @@ export default class Query extends this.queryObservable = null; this.previousData = {}; this.updateQuery(nextProps); - } else if (nextProps.shouldInvalidatePreviousData) { - if (nextProps.shouldInvalidatePreviousData(nextProps.variables, this.props.variables)) { - this.previousData = {}; - } - } else if (!shallowEqual(nextProps.variables, this.props.variables)) { + } else if (nextProps.shouldInvalidatePreviousData(nextProps.variables, this.props.variables)) { this.previousData = {}; } @@ -262,7 +268,7 @@ export default class Query extends ...props, displayName, context: props.context || {}, - metadata: { reactComponent: { displayName }}, + metadata: { reactComponent: { displayName } }, }; } diff --git a/src/utils/shallowEqual.ts b/src/utils/shallowEqual.ts index b35dd0fb5a..5ebc177c05 100644 --- a/src/utils/shallowEqual.ts +++ b/src/utils/shallowEqual.ts @@ -1,3 +1,4 @@ +type AnyObject = { [key: string]: any }; const { hasOwnProperty } = Object.prototype; function is(x: any, y: any) { @@ -7,11 +8,21 @@ function is(x: any, y: any) { return x !== x && y !== y; } -function isObject(obj: any): obj is { [key: string]: any } { +function isObject(obj: any): obj is AnyObject { return obj !== null && typeof obj === "object"; } -export default function shallowEqual(objA: any, objB: any) { +function shallowObjectEqual(objA: AnyObject, objB: AnyObject) { + const keys = Object.keys(objA); + + if (keys.length !== Object.keys(objB).length) { + return false; + } + + return keys.every(key => hasOwnProperty.call(objB, key) && is(objA[key], objB[key])); +} + +export function shallowEqualSansFirst(objA: any, objB: any) { if (is(objA, objB)) { return true; } @@ -20,13 +31,21 @@ export default function shallowEqual(objA: any, objB: any) { return false; } - const keys = Object.keys(objA); + // Ignore `first` key + const { first: _, ...objectA } = objA; + const { first: __, ...objectB } = objB; - if (keys.length !== Object.keys(objB).length) { + return shallowObjectEqual(objectA, objectB); +} + +export default function shallowEqual(objA: any, objB: any) { + if (is(objA, objB)) { + return true; + } + + if (!isObject(objA) || !isObject(objB)) { return false; } - return keys.every( - key => hasOwnProperty.call(objB, key) && is(objA[key], objB[key]), - ); + return shallowObjectEqual(objA, objB); }