From 6efdedcb3116f29d2e645333f6c8226596ab4fba Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 2 Jan 2025 11:52:37 -0800 Subject: [PATCH] remove experimental from prefetch pagination hook in fbsource Reviewed By: tyao1 Differential Revision: D67533853 fbshipit-source-id: 04bede60afa142b0a5f646715513728eeb02ef61 --- packages/react-relay/index.js | 6 +- ...dPaginationFragmentRefetchQuery.graphql.js | 14 +- ...ardPaginationFragmentTestQuery.graphql.js} | 34 +- ...ardPaginationFragmentTest_user.graphql.js} | 34 +- ...nationFragmentTest_user__edges.graphql.js} | 20 +- ...etchableForwardPaginationFragment-test.js} | 14 +- ...sePrefetchableForwardPaginationFragment.js | 433 ++++++++++++++++++ ...refetchable-forward-pagination-fragment.md | 8 +- 8 files changed, 498 insertions(+), 65 deletions(-) rename packages/react-relay/relay-hooks/__tests__/__generated__/{usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery.graphql.js => usePrefetchableForwardPaginationFragmentTestQuery.graphql.js} (75%) rename packages/react-relay/relay-hooks/__tests__/__generated__/{usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user.graphql.js => usePrefetchableForwardPaginationFragmentTest_user.graphql.js} (74%) rename packages/react-relay/relay-hooks/__tests__/__generated__/{usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges.graphql.js => usePrefetchableForwardPaginationFragmentTest_user__edges.graphql.js} (65%) rename packages/react-relay/relay-hooks/__tests__/{usePrefetchableForwardPaginationFragment_EXPERIMENTAL-test.js => usePrefetchableForwardPaginationFragment-test.js} (97%) create mode 100644 packages/react-relay/relay-hooks/usePrefetchableForwardPaginationFragment.js diff --git a/packages/react-relay/index.js b/packages/react-relay/index.js index a9e1520fd96e7..238e8d1a39d7d 100644 --- a/packages/react-relay/index.js +++ b/packages/react-relay/index.js @@ -28,7 +28,7 @@ const useFragment = require('./relay-hooks/useFragment'); const useLazyLoadQuery = require('./relay-hooks/useLazyLoadQuery'); const useMutation = require('./relay-hooks/useMutation'); const usePaginationFragment = require('./relay-hooks/usePaginationFragment'); -const usePrefetchableForwardPaginationFragment_EXPERIMENTAL = require('./relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL'); +const usePrefetchableForwardPaginationFragment = require('./relay-hooks/usePrefetchableForwardPaginationFragment'); const usePreloadedQuery = require('./relay-hooks/usePreloadedQuery'); const useQueryLoader = require('./relay-hooks/useQueryLoader'); const useRefetchableFragment = require('./relay-hooks/useRefetchableFragment'); @@ -126,8 +126,8 @@ module.exports = { usePaginationFragment: usePaginationFragment, usePreloadedQuery: usePreloadedQuery, useRefetchableFragment: useRefetchableFragment, - usePrefetchableForwardPaginationFragment_EXPERIMENTAL: - usePrefetchableForwardPaginationFragment_EXPERIMENTAL, + usePrefetchableForwardPaginationFragment: + usePrefetchableForwardPaginationFragment, useRelayEnvironment: useRelayEnvironment, useSubscribeToInvalidationState: useSubscribeToInvalidationState, useSubscription: useSubscription, diff --git a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentRefetchQuery.graphql.js b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentRefetchQuery.graphql.js index 446a48323bcaa..4df767a71168f 100644 --- a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentRefetchQuery.graphql.js +++ b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentRefetchQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<<569024641be3b0efc2ca9e9f5b020b7e>> + * @generated SignedSource<<919069c963b6880374b876874c00ed31>> * @flow * @lightSyntaxTransform * @nogrep @@ -19,7 +19,7 @@ /*:: import type { ConcreteRequest, Query } from 'relay-runtime'; import type { FragmentType } from "relay-runtime"; -import type { usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType } from "./usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user.graphql"; +import type { usePrefetchableForwardPaginationFragmentTest_user$fragmentType } from "./usePrefetchableForwardPaginationFragmentTest_user.graphql"; export type usePrefetchableForwardPaginationFragmentRefetchQuery$variables = {| after?: ?string, before?: ?string, @@ -29,7 +29,7 @@ export type usePrefetchableForwardPaginationFragmentRefetchQuery$variables = {| |}; export type usePrefetchableForwardPaginationFragmentRefetchQuery$data = {| +node: ?{| - +$fragmentSpreads: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType, + +$fragmentSpreads: usePrefetchableForwardPaginationFragmentTest_user$fragmentType, |}, |}; export type usePrefetchableForwardPaginationFragmentRefetchQuery = {| @@ -131,7 +131,7 @@ return { { "args": null, "kind": "FragmentSpread", - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user" + "name": "usePrefetchableForwardPaginationFragmentTest_user" } ], "storageKey": null @@ -272,18 +272,18 @@ return { ] }, "params": { - "cacheID": "392f33f21d5b262df1a92d3547ae09bc", + "cacheID": "2f9567322cbafd63725e67eac1c69356", "id": null, "metadata": {}, "name": "usePrefetchableForwardPaginationFragmentRefetchQuery", "operationKind": "query", - "text": "query usePrefetchableForwardPaginationFragmentRefetchQuery(\n $after: ID\n $before: ID\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user\n id\n }\n}\n\nfragment usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user on User {\n friends(after: $after, first: $first, before: $before, last: $last) {\n edges {\n ...usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges on FriendsEdge {\n node {\n id\n name\n __typename\n }\n cursor\n}\n" + "text": "query usePrefetchableForwardPaginationFragmentRefetchQuery(\n $after: ID\n $before: ID\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...usePrefetchableForwardPaginationFragmentTest_user\n id\n }\n}\n\nfragment usePrefetchableForwardPaginationFragmentTest_user on User {\n friends(after: $after, first: $first, before: $before, last: $last) {\n edges {\n ...usePrefetchableForwardPaginationFragmentTest_user__edges\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment usePrefetchableForwardPaginationFragmentTest_user__edges on FriendsEdge {\n node {\n id\n name\n __typename\n }\n cursor\n}\n" } }; })(); if (__DEV__) { - (node/*: any*/).hash = "809455a8d7ada67cb84f3d111f6c6010"; + (node/*: any*/).hash = "b556c89ea274871519ed4779f197956d"; } module.exports = ((node/*: any*/)/*: Query< diff --git a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery.graphql.js b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTestQuery.graphql.js similarity index 75% rename from packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery.graphql.js rename to packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTestQuery.graphql.js index 21f05505b5df6..3412ccfa11447 100644 --- a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery.graphql.js +++ b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTestQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<<8a5cc6645690b01d77261e637131b458>> + * @generated SignedSource<<4fa63c4936977c25e7c3024a79b1ea99>> * @flow * @lightSyntaxTransform * @nogrep @@ -18,22 +18,22 @@ /*:: import type { ConcreteRequest, Query } from 'relay-runtime'; -import type { usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType } from "./usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user.graphql"; -export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery$variables = {| +import type { usePrefetchableForwardPaginationFragmentTest_user$fragmentType } from "./usePrefetchableForwardPaginationFragmentTest_user.graphql"; +export type usePrefetchableForwardPaginationFragmentTestQuery$variables = {| after?: ?string, before?: ?string, first?: ?number, id: string, last?: ?number, |}; -export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery$data = {| +export type usePrefetchableForwardPaginationFragmentTestQuery$data = {| +node: ?{| - +$fragmentSpreads: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType, + +$fragmentSpreads: usePrefetchableForwardPaginationFragmentTest_user$fragmentType, |}, |}; -export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery = {| - response: usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery$data, - variables: usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery$variables, +export type usePrefetchableForwardPaginationFragmentTestQuery = {| + response: usePrefetchableForwardPaginationFragmentTestQuery$data, + variables: usePrefetchableForwardPaginationFragmentTestQuery$variables, |}; */ @@ -117,7 +117,7 @@ return { ], "kind": "Fragment", "metadata": null, - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery", + "name": "usePrefetchableForwardPaginationFragmentTestQuery", "selections": [ { "alias": null, @@ -130,7 +130,7 @@ return { { "args": null, "kind": "FragmentSpread", - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user" + "name": "usePrefetchableForwardPaginationFragmentTest_user" } ], "storageKey": null @@ -149,7 +149,7 @@ return { (v4/*: any*/) ], "kind": "Operation", - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery", + "name": "usePrefetchableForwardPaginationFragmentTestQuery", "selections": [ { "alias": null, @@ -271,21 +271,21 @@ return { ] }, "params": { - "cacheID": "6cd62bc50d478e401286fbd253466497", + "cacheID": "9e9b011d9b52d31f16e8e2ee376822bb", "id": null, "metadata": {}, - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery", + "name": "usePrefetchableForwardPaginationFragmentTestQuery", "operationKind": "query", - "text": "query usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery(\n $id: ID!\n $after: ID\n $first: Int\n $before: ID\n $last: Int\n) {\n node(id: $id) {\n __typename\n ...usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user\n id\n }\n}\n\nfragment usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user on User {\n friends(after: $after, first: $first, before: $before, last: $last) {\n edges {\n ...usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges on FriendsEdge {\n node {\n id\n name\n __typename\n }\n cursor\n}\n" + "text": "query usePrefetchableForwardPaginationFragmentTestQuery(\n $id: ID!\n $after: ID\n $first: Int\n $before: ID\n $last: Int\n) {\n node(id: $id) {\n __typename\n ...usePrefetchableForwardPaginationFragmentTest_user\n id\n }\n}\n\nfragment usePrefetchableForwardPaginationFragmentTest_user on User {\n friends(after: $after, first: $first, before: $before, last: $last) {\n edges {\n ...usePrefetchableForwardPaginationFragmentTest_user__edges\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment usePrefetchableForwardPaginationFragmentTest_user__edges on FriendsEdge {\n node {\n id\n name\n __typename\n }\n cursor\n}\n" } }; })(); if (__DEV__) { - (node/*: any*/).hash = "48bd7cb20fb4977c3582b839ce846c1f"; + (node/*: any*/).hash = "2794c437db678dcb4f93dd23edaa4055"; } module.exports = ((node/*: any*/)/*: Query< - usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery$variables, - usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery$data, + usePrefetchableForwardPaginationFragmentTestQuery$variables, + usePrefetchableForwardPaginationFragmentTestQuery$data, >*/); diff --git a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user.graphql.js b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTest_user.graphql.js similarity index 74% rename from packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user.graphql.js rename to packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTest_user.graphql.js index 20eadaa490dfd..2e5388f8e08dc 100644 --- a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user.graphql.js +++ b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTest_user.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<<369c689b8624b2aa4e3cea4ad88d864a>> + * @generated SignedSource<<8becc1a8ce7b77e8e0c4d71b66d7450f>> * @flow * @lightSyntaxTransform * @nogrep @@ -18,15 +18,15 @@ /*:: import type { ReaderFragment, PrefetchableRefetchableFragment } from 'relay-runtime'; -import type { usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$fragmentType } from "./usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges.graphql"; +import type { usePrefetchableForwardPaginationFragmentTest_user__edges$fragmentType } from "./usePrefetchableForwardPaginationFragmentTest_user__edges.graphql"; import type { FragmentType } from "relay-runtime"; -declare export opaque type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType: FragmentType; +declare export opaque type usePrefetchableForwardPaginationFragmentTest_user$fragmentType: FragmentType; type usePrefetchableForwardPaginationFragmentRefetchQuery$variables = any; -type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$data = any; -export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$data = {| +type usePrefetchableForwardPaginationFragmentTest_user__edges$data = any; +export type usePrefetchableForwardPaginationFragmentTest_user$data = {| +friends: ?{| +edges: ?$ReadOnlyArray, +pageInfo: ?{| +endCursor: ?string, @@ -36,11 +36,11 @@ export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$data = |}, |}, +id: string, - +$fragmentType: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType, + +$fragmentType: usePrefetchableForwardPaginationFragmentTest_user$fragmentType, |}; -export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$key = { - +$data?: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$data, - +$fragmentSpreads: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType, +export type usePrefetchableForwardPaginationFragmentTest_user$key = { + +$data?: usePrefetchableForwardPaginationFragmentTest_user$data, + +$fragmentSpreads: usePrefetchableForwardPaginationFragmentTest_user$fragmentType, ... }; */ @@ -98,10 +98,10 @@ return { "identifierField": "id", "identifierQueryVariableName": "id" }, - "edgesFragment": require('./usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges.graphql') + "edgesFragment": require('./usePrefetchableForwardPaginationFragmentTest_user__edges.graphql') } }, - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user", + "name": "usePrefetchableForwardPaginationFragmentTest_user", "selections": [ { "alias": "friends", @@ -122,7 +122,7 @@ return { { "args": null, "kind": "FragmentSpread", - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges" + "name": "usePrefetchableForwardPaginationFragmentTest_user__edges" } ], "storageKey": null @@ -183,12 +183,12 @@ return { })(); if (__DEV__) { - (node/*: any*/).hash = "809455a8d7ada67cb84f3d111f6c6010"; + (node/*: any*/).hash = "b556c89ea274871519ed4779f197956d"; } module.exports = ((node/*: any*/)/*: PrefetchableRefetchableFragment< - usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$fragmentType, - usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$data, - usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$data, + usePrefetchableForwardPaginationFragmentTest_user$fragmentType, + usePrefetchableForwardPaginationFragmentTest_user$data, + usePrefetchableForwardPaginationFragmentTest_user__edges$data, usePrefetchableForwardPaginationFragmentRefetchQuery$variables, >*/); diff --git a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges.graphql.js b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTest_user__edges.graphql.js similarity index 65% rename from packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges.graphql.js rename to packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTest_user__edges.graphql.js index 1ebdea9b752da..741fcda827535 100644 --- a/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges.graphql.js +++ b/packages/react-relay/relay-hooks/__tests__/__generated__/usePrefetchableForwardPaginationFragmentTest_user__edges.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<> * @flow * @lightSyntaxTransform * @nogrep @@ -19,19 +19,19 @@ /*:: import type { Fragment, ReaderFragment } from 'relay-runtime'; import type { FragmentType } from "relay-runtime"; -declare export opaque type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$fragmentType: FragmentType; -export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$data = $ReadOnlyArray<{| +declare export opaque type usePrefetchableForwardPaginationFragmentTest_user__edges$fragmentType: FragmentType; +export type usePrefetchableForwardPaginationFragmentTest_user__edges$data = $ReadOnlyArray<{| +cursor: ?string, +node: ?{| +__typename: "User", +id: string, +name: ?string, |}, - +$fragmentType: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$fragmentType, + +$fragmentType: usePrefetchableForwardPaginationFragmentTest_user__edges$fragmentType, |}>; -export type usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$key = $ReadOnlyArray<{ - +$data?: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$data, - +$fragmentSpreads: usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$fragmentType, +export type usePrefetchableForwardPaginationFragmentTest_user__edges$key = $ReadOnlyArray<{ + +$data?: usePrefetchableForwardPaginationFragmentTest_user__edges$data, + +$fragmentSpreads: usePrefetchableForwardPaginationFragmentTest_user__edges$fragmentType, ... }>; */ @@ -42,7 +42,7 @@ var node/*: ReaderFragment*/ = { "metadata": { "plural": true }, - "name": "usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges", + "name": "usePrefetchableForwardPaginationFragmentTest_user__edges", "selections": [ { "alias": null, @@ -89,6 +89,6 @@ var node/*: ReaderFragment*/ = { }; module.exports = ((node/*: any*/)/*: Fragment< - usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$fragmentType, - usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user__edges$data, + usePrefetchableForwardPaginationFragmentTest_user__edges$fragmentType, + usePrefetchableForwardPaginationFragmentTest_user__edges$data, >*/); diff --git a/packages/react-relay/relay-hooks/__tests__/usePrefetchableForwardPaginationFragment_EXPERIMENTAL-test.js b/packages/react-relay/relay-hooks/__tests__/usePrefetchableForwardPaginationFragment-test.js similarity index 97% rename from packages/react-relay/relay-hooks/__tests__/usePrefetchableForwardPaginationFragment_EXPERIMENTAL-test.js rename to packages/react-relay/relay-hooks/__tests__/usePrefetchableForwardPaginationFragment-test.js index c289622f2d2d9..f62e6b6e55220 100644 --- a/packages/react-relay/relay-hooks/__tests__/usePrefetchableForwardPaginationFragment_EXPERIMENTAL-test.js +++ b/packages/react-relay/relay-hooks/__tests__/usePrefetchableForwardPaginationFragment-test.js @@ -11,9 +11,9 @@ 'use strict'; -import type {usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$key} from './__generated__/usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user.graphql'; +import type {usePrefetchableForwardPaginationFragmentTest_user$key} from './__generated__/usePrefetchableForwardPaginationFragmentTest_user.graphql'; -const usePrefetchableForwardPaginationFragment_EXPERIMENTAL = require('../usePrefetchableForwardPaginationFragment_EXPERIMENTAL'); +const usePrefetchableForwardPaginationFragment = require('../usePrefetchableForwardPaginationFragment'); const React = require('react'); const {RelayEnvironmentProvider} = require('react-relay/hooks'); const {act, create} = require('react-test-renderer'); @@ -37,7 +37,7 @@ let hasNextSpy; let isLoadingNextSpy; component Container( - userRef: ?usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user$key, + userRef: ?usePrefetchableForwardPaginationFragmentTest_user$key, minimalEdgesToFetch: number = 1, UNSTABLE_extraVariables?: mixed, ) { @@ -48,9 +48,9 @@ component Container( refetch: _refetch, hasNext, isLoadingNext, - } = usePrefetchableForwardPaginationFragment_EXPERIMENTAL( + } = usePrefetchableForwardPaginationFragment( graphql` - fragment usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user on User + fragment usePrefetchableForwardPaginationFragmentTest_user on User @refetchable( queryName: "usePrefetchableForwardPaginationFragmentRefetchQuery" ) { @@ -105,7 +105,7 @@ beforeEach(() => { query = createOperationDescriptor( graphql` - query usePrefetchableForwardPaginationFragmentEXPERIMENTALTestQuery( + query usePrefetchableForwardPaginationFragmentTestQuery( $id: ID! $after: ID $first: Int @@ -113,7 +113,7 @@ beforeEach(() => { $last: Int ) { node(id: $id) { - ...usePrefetchableForwardPaginationFragmentEXPERIMENTALTest_user + ...usePrefetchableForwardPaginationFragmentTest_user } } `, diff --git a/packages/react-relay/relay-hooks/usePrefetchableForwardPaginationFragment.js b/packages/react-relay/relay-hooks/usePrefetchableForwardPaginationFragment.js new file mode 100644 index 0000000000000..db380ca2f0ce2 --- /dev/null +++ b/packages/react-relay/relay-hooks/usePrefetchableForwardPaginationFragment.js @@ -0,0 +1,433 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall relay + */ + +'use strict'; + +import type {RefetchFn} from './useRefetchableFragment'; +import type {Options} from './useRefetchableFragmentInternal'; +import type {FragmentType, Variables} from 'relay-runtime'; +import type {PrefetchableRefetchableFragment} from 'relay-runtime'; + +const useFragment = require('./useFragment'); +const useLoadMoreFunction = require('./useLoadMoreFunction'); +const useRefetchableFragmentInternal = require('./useRefetchableFragmentInternal'); +const useRelayEnvironment = require('./useRelayEnvironment'); +const useStaticFragmentNodeWarning = require('./useStaticFragmentNodeWarning'); +const invariant = require('invariant'); +const { + useCallback, + useDebugValue, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} = require('react'); +const { + getFragment, + getFragmentIdentifier, + getPaginationMetadata, +} = require('relay-runtime'); +const { + ConnectionInterface, + RelayFeatureFlags, + getSelector, + getValueAtPath, +} = require('relay-runtime'); + +type LoadMoreFn = ( + count: number, + options?: { + onComplete?: (Error | null) => void, + UNSTABLE_extraVariables?: Partial, + }, +) => void; + +export type ReturnType = { + // NOTE: This type ensures that the type of the returned data is either: + // - nullable if the provided ref type is nullable + // - non-nullable if the provided ref type is non-nullable + data: [+key: TKey] extends [+key: {+$fragmentSpreads: mixed, ...}] + ? TData + : ?TData, + loadNext: LoadMoreFn, + hasNext: boolean, + isLoadingNext: boolean, + refetch: RefetchFn, + edges: TEdgeData, +}; + +type LoadMoreOptions = { + UNSTABLE_extraVariables?: Partial, + onComplete?: (Error | null) => void, +}; + +export type GetExtraVariablesFn = ({ + hasNext: boolean, + data: [+key: TKey] extends [+key: {+$fragmentSpreads: mixed, ...}] + ? TData + : ?TData, + getServerEdges: () => TEdgeData, +}) => Partial; + +hook usePrefetchableForwardPaginationFragment< + TFragmentType: FragmentType, + TVariables: Variables, + TData, + TEdgeData, + TKey: ?{+$fragmentSpreads: TFragmentType, ...}, +>( + fragmentInput: PrefetchableRefetchableFragment< + TFragmentType, + TData, + TEdgeData, + TVariables, + >, + parentFragmentRef: TKey, + bufferSize: number, + initialSize?: ?number, + prefetchingLoadMoreOptions?: { + UNSTABLE_extraVariables?: + | Partial + | GetExtraVariablesFn, + onComplete?: (Error | null) => void, + }, + minimalFetchSize: number = 1, +): ReturnType { + const fragmentNode = getFragment(fragmentInput); + useStaticFragmentNodeWarning( + fragmentNode, + 'first argument of usePrefetchableForwardPaginationFragment()', + ); + const componentDisplayName = 'usePrefetchableForwardPaginationFragment()'; + + const {connectionPathInFragmentData, paginationRequest, paginationMetadata} = + getPaginationMetadata(fragmentNode, componentDisplayName); + + const {fragmentData, fragmentRef, refetch} = useRefetchableFragmentInternal< + {variables: TVariables, response: TData}, + {data?: TData}, + >(fragmentNode, parentFragmentRef, componentDisplayName); + // TODO: Get rid of `getFragmentIdentifier` + const fragmentIdentifier = getFragmentIdentifier(fragmentNode, fragmentRef); + + const edgeKeys = useMemo(() => { + const connection = getValueAtPath( + fragmentData, + connectionPathInFragmentData, + ); + if (connection == null) { + return null; + } + const {EDGES} = ConnectionInterface.get(); + // $FlowFixMe[incompatible-use] + return connection[EDGES]; + }, [connectionPathInFragmentData, fragmentData]); + + const sourceSize = edgeKeys == null ? -1 : edgeKeys.length; + + const [_numInUse, setNumInUse] = useState( + initialSize != null ? initialSize : sourceSize, + ); + let numInUse = _numInUse; + // We can only reset the source size when the component is + // updated with new edgeKeys + if (_numInUse === -1 && sourceSize !== -1) { + numInUse = initialSize != null ? initialSize : sourceSize; + setNumInUse(numInUse); + } + + const environment = useRelayEnvironment(); + const [isLoadingMore, reallySetIsLoadingMore] = useState(false); + const [isRefetching, setIsRefetching] = useState(false); + const availableSizeRef = useRef(0); + // Schedule this update since it must be observed by components at the same + // batch as when hasNext changes. hasNext is read from the store and store + // updates are scheduled, so this must be scheduled too. + const setIsLoadingMore = useCallback( + (value: boolean) => { + const schedule = environment.getScheduler()?.schedule; + if (schedule) { + schedule(() => { + reallySetIsLoadingMore(value); + }); + } else { + reallySetIsLoadingMore(value); + } + }, + [environment], + ); + + // `isLoadingMore` state is updated in a low priority, internally we need + // to synchronously get the loading state to decide whether to load more + const isLoadingMoreRef = useRef(false); + + const observer = useMemo(() => { + function setIsLoadingFalse() { + isLoadingMoreRef.current = false; + setIsLoadingMore(false); + } + return { + start: () => { + isLoadingMoreRef.current = true; + // We want to make sure that `isLoadingMore` is updated immediately, to avoid + // product code triggering multiple `loadMore` calls + reallySetIsLoadingMore(true); + }, + complete: setIsLoadingFalse, + error: setIsLoadingFalse, + unsubscribe: RelayFeatureFlags.ENABLE_USE_PAGINATION_IS_LOADING_FIX + ? setIsLoadingFalse + : undefined, + }; + }, [setIsLoadingMore]); + const handleReset = useCallback(() => { + if (!isRefetching) { + // Do not reset items count during refetching + const schedule = environment.getScheduler()?.schedule; + if (schedule) { + schedule(() => { + setNumInUse(-1); + }); + } else { + setNumInUse(-1); + } + } + isLoadingMoreRef.current = false; + setIsLoadingMore(false); + }, [environment, isRefetching, setIsLoadingMore]); + + const [loadMore, hasNext, disposeFetchNext] = useLoadMoreFunction( + { + componentDisplayName, + connectionPathInFragmentData, + direction: 'forward', + fragmentData, + fragmentIdentifier, + fragmentNode, + fragmentRef, + paginationMetadata, + paginationRequest, + observer, + onReset: handleReset, + }, + ); + + useLayoutEffect(() => { + // Make sure `availableSize` is updated before `showMore` from current render can be called + availableSizeRef.current = sourceSize - numInUse; + }, [numInUse, sourceSize]); + + const prefetchingUNSTABLE_extraVariables = + prefetchingLoadMoreOptions?.UNSTABLE_extraVariables; + const prefetchingOnComplete = prefetchingLoadMoreOptions?.onComplete; + + const showMore = useCallback( + (numToAdd: number, options?: LoadMoreOptions) => { + // Matches the behavior of `usePaginationFragment`. If there is a `loadMore` ongoing, + // the hook handles making the `loadMore` a no-op. + if (!isLoadingMoreRef.current || availableSizeRef.current >= 0) { + // Preemtively update `availableSizeRef`, so if two `loadMore` is called in the same tick, + // a second `loadMore` can be no-op + availableSizeRef.current -= numToAdd; + + setNumInUse(lastNumInUse => { + return lastNumInUse + numToAdd; + }); + + // If the product needs more items from network, load the amount needed to fullfil + // the requirement and cache, capped at the current amount defined by product + if (!isLoadingMoreRef.current && availableSizeRef.current < 0) { + loadMore( + Math.max( + minimalFetchSize, + Math.min(numToAdd, bufferSize - availableSizeRef.current), + ), + // Keep options For backward compatibility + options ?? { + onComplete: prefetchingOnComplete, + UNSTABLE_extraVariables: + typeof prefetchingUNSTABLE_extraVariables === 'function' + ? // $FlowFixMe[incompatible-call] + prefetchingUNSTABLE_extraVariables({ + hasNext, + // $FlowFixMe[incompatible-call] + data: fragmentData, + getServerEdges: () => { + const selector = getSelector( + // $FlowFixMe[incompatible-call] + edgesFragment, + edgeKeys, + ); + if (selector == null) { + // $FlowFixMe[incompatible-call] + return []; + } + invariant( + selector.kind === 'PluralReaderSelector', + 'Expected a plural selector', + ); + // $FlowFixMe[incompatible-call] + return selector.selectors.map( + sel => environment.lookup(sel).data, + ); + }, + }) + : prefetchingUNSTABLE_extraVariables, + }, + ); + } + } + }, + [ + bufferSize, + loadMore, + minimalFetchSize, + edgeKeys, + fragmentData, + prefetchingUNSTABLE_extraVariables, + prefetchingOnComplete, + ], + ); + + const edgesFragment = fragmentInput.metadata?.refetch?.edgesFragment; + invariant( + edgesFragment != null, + 'usePrefetchableForwardPaginationFragment: Expected the edge fragment to be defined, ' + + 'please make sure you have added `prefetchable_pagination: true` to `@connection`', + ); + + // Always try to keep `bufferSize` items in the buffer + // Or load the number of items that have been registred to show + useEffect(() => { + if ( + // Check the ref to avoid infinite `loadMore`, when a `loadMore` has started, + // but `isLoadingMore` isn't updated + !isLoadingMoreRef.current && + // Check the original `isLoadingMore` so when `loadMore` is called, the internal + // `loadMore` hook has been updated with the latest cursor + !isLoadingMore && + !isRefetching && + hasNext && + (sourceSize - numInUse < bufferSize || numInUse > sourceSize) + ) { + const onComplete = prefetchingOnComplete; + loadMore( + Math.max( + bufferSize - Math.max(sourceSize - numInUse, 0), + numInUse - sourceSize, + minimalFetchSize, + ), + { + onComplete, + UNSTABLE_extraVariables: + typeof prefetchingUNSTABLE_extraVariables === 'function' + ? // $FlowFixMe[incompatible-call] + prefetchingUNSTABLE_extraVariables({ + hasNext, + // $FlowFixMe[incompatible-call] + data: fragmentData, + getServerEdges: () => { + const selector = getSelector(edgesFragment, edgeKeys); + if (selector == null) { + // $FlowFixMe[incompatible-call] + return []; + } + invariant( + selector.kind === 'PluralReaderSelector', + 'Expected a plural selector', + ); + // $FlowFixMe[incompatible-call] + return selector.selectors.map( + sel => environment.lookup(sel).data, + ); + }, + }) + : prefetchingUNSTABLE_extraVariables, + }, + ); + } + }, [ + hasNext, + bufferSize, + isRefetching, + loadMore, + numInUse, + prefetchingUNSTABLE_extraVariables, + prefetchingOnComplete, + sourceSize, + edgeKeys, + isLoadingMore, + minimalFetchSize, + environment, + edgesFragment, + ]); + + const realNumInUse = Math.min(numInUse, sourceSize); + + const derivedEdgeKeys: $ReadOnlyArray = useMemo( + () => edgeKeys?.slice(0, realNumInUse) ?? [], + [edgeKeys, realNumInUse], + ); + + // $FlowExpectedError[incompatible-call] - we know derivedEdgeKeys are the correct keys + const edges: TEdgeData = useFragment(edgesFragment, derivedEdgeKeys); + + const refetchPagination = useCallback( + (variables: TVariables, options?: Options) => { + disposeFetchNext(); + setIsRefetching(true); + return refetch(variables, { + ...options, + onComplete: maybeError => { + // Need to be batched with the store update + const schedule = environment.getScheduler()?.schedule; + if (schedule) { + schedule(() => { + setIsRefetching(false); + setNumInUse(-1); + }); + } else { + setIsRefetching(false); + setNumInUse(-1); + } + options?.onComplete?.(maybeError); + }, + __environment: undefined, + }); + }, + [disposeFetchNext, environment, refetch], + ); + + if (__DEV__) { + // $FlowFixMe[react-rule-hook] + useDebugValue({ + fragment: fragmentNode.name, + data: fragmentData, + hasNext, + isLoadingNext: isLoadingMore, + }); + } + + return { + edges, + // $FlowFixMe[incompatible-return] + data: fragmentData, + loadNext: showMore, + hasNext: hasNext || sourceSize > numInUse, + // Only reflect `isLoadingMore` if the product depends on it, do not refelect + // `isLoaindgMore` state if it is for fufilling the buffer + isLoadingNext: isLoadingMore && numInUse > sourceSize, + refetch: refetchPagination, + }; +} + +module.exports = usePrefetchableForwardPaginationFragment; diff --git a/website/docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.md b/website/docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.md index 296e615d9a6ef..0cb81b1a6afb6 100644 --- a/website/docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.md +++ b/website/docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.md @@ -2,7 +2,7 @@ id: use-prefetchable-forward-pagination-fragment title: usePrefetchableForwardPaginationFragment slug: /api-reference/use-prefetchable-forward-pagination-fragment/ -description: API reference for usePrefetchableForwardPaginationFragment_EXPERIMENTAL, an experimental React hook used to paginate a connection and automatically prefetches +description: API reference for usePrefetchableForwardPaginationFragment, an experimental React hook used to paginate a connection and automatically prefetches keywords: - pagination - connection @@ -12,14 +12,14 @@ keywords: import DocsRating from '@site/src/core/DocsRating'; NOTE: This is an experimental API and may be subject to change. -`usePrefetchableForwardPaginationFragment_EXPERIMENTAL` is similar to [`usePaginationFragment`](../use-pagination-fragment). It adds the capability to automatically prefetch a `bufferSize` number of items to fill the buffer without displaying the items. And when `loadNext` is called, it vends from the buffer first to achieve faster pagination. It only supports forward pagination (provides APIs for `loadNext`, `hasNext` and `isLoadingNext`) for now. +`usePrefetchableForwardPaginationFragment` is similar to [`usePaginationFragment`](../use-pagination-fragment). It adds the capability to automatically prefetch a `bufferSize` number of items to fill the buffer without displaying the items. And when `loadNext` is called, it vends from the buffer first to achieve faster pagination. It only supports forward pagination (provides APIs for `loadNext`, `hasNext` and `isLoadingNext`) for now. ```js import type {FriendsList_user$key} from 'FriendsList_user.graphql'; const React = require('React'); -const {graphql, usePrefetchableForwardPaginationFragment_EXPERIMENTAL} = require('react-relay'); +const {graphql, usePrefetchableForwardPaginationFragment} = require('react-relay'); type Props = { user: FriendsList_user$key, @@ -33,7 +33,7 @@ function FriendsList(props: Props) { hasNext, isLoadingNext, refetch, // For refetching connection - } = usePrefetchableForwardPaginationFragment_EXPERIMENTAL( + } = usePrefetchableForwardPaginationFragment( graphql` fragment FriendsListComponent_user on User @refetchable(queryName: "FriendsListPaginationQuery") {