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

Feature: useSuspenseFragment #12066

Draft
wants to merge 36 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f1d95ce
Add way to cache fragment promises
jerelmiller Sep 17, 2024
9b6a6bc
Add some code to handle suspenseful useFragment
jerelmiller Sep 18, 2024
57b085f
Add handling of promise state in FragmentReference
jerelmiller Sep 18, 2024
b5906c9
Add some basic tests for useSuspenseFragment
jerelmiller Sep 18, 2024
5bb73d6
Add exports for useSuspenseFragment
jerelmiller Sep 18, 2024
5a62499
Remove unused code
jerelmiller Sep 18, 2024
2a130cd
Update exports snapshot
jerelmiller Sep 18, 2024
cb0c2f8
Wrap useSuspenseFragment hook for streaming package
jerelmiller Sep 18, 2024
296fe26
Handle different cache ids in useSuspenseFragment
jerelmiller Sep 18, 2024
f230ebb
Update tests to use render stream library
jerelmiller Dec 17, 2024
2870a81
Add useSuspenseFragment to list of ignored React 17 tests
jerelmiller Dec 17, 2024
0aa28be
Fix types to work with data masking
jerelmiller Dec 17, 2024
8a0e81f
Add test that ensures error is thrown when passing non-fragment to us…
jerelmiller Dec 17, 2024
39a253c
Inline the cache key
jerelmiller Dec 17, 2024
94fdd59
Add test for complete result on first render
jerelmiller Dec 17, 2024
189775d
Create observable in FragmentReference
jerelmiller Dec 17, 2024
7ec0be7
Handle case where cache data is already written
jerelmiller Dec 17, 2024
5810d12
Fix typo
jerelmiller Dec 17, 2024
173ea46
Add test to ensure cache updates are received after initial result
jerelmiller Dec 17, 2024
8aba146
Add test to ensure data can be overwritten
jerelmiller Dec 17, 2024
b303a0a
Add test to ensure client is provided
jerelmiller Dec 17, 2024
fc1adc4
Add test to ensure changing from values suspends correctly
jerelmiller Dec 17, 2024
ce07586
Add test for changing from with data already written to cache
jerelmiller Dec 17, 2024
a73e833
Add test to check @nonreactive
jerelmiller Dec 17, 2024
81333fd
Add test for @nonreactive with nested fragment
jerelmiller Dec 17, 2024
4fb5f7a
Add failing test for suspending when not passing key fields to fragment
jerelmiller Dec 17, 2024
e594fe1
Use deep equality in FragmentReference
jerelmiller Dec 17, 2024
db7ce47
Add tests for data masking
jerelmiller Dec 17, 2024
41a4d3e
Add test to check for cache updates to masked fields
jerelmiller Dec 17, 2024
6adae3d
Add support for `null` value as `from` in `useSuspenseFragment`
jerelmiller Dec 18, 2024
7e08d1d
Add test to ensure changing from null works as expected
jerelmiller Dec 18, 2024
bb519b7
Remove unneeded type cast
jerelmiller Dec 18, 2024
0df7960
Add test to ensure useSuspenseFragment suspends when switching from null
jerelmiller Dec 18, 2024
286f4c3
Add test to ensure switching from non-null to null works as expected
jerelmiller Dec 18, 2024
2fa1a18
Update api report and size limits
jerelmiller Dec 18, 2024
9aa3eed
Add changeset
jerelmiller Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .api-reports/api-report-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2416,6 +2416,40 @@ export function useSubscription<TData = any, TVariables extends OperationVariabl
variables?: TVariables | undefined;
};

// Warning: (ae-forgotten-export) The symbol "UseSuspenseFragmentOptions" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {};
}): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: null;
}): UseSuspenseFragmentResult<null>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {} | null;
}): UseSuspenseFragmentResult<TData | null>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables>): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
interface UseSuspenseFragmentOptions<TData, TVars> extends Omit<Cache_2.DiffOptions<NoInfer<TData>, NoInfer<TVars>>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit<Cache_2.ReadFragmentOptions<TData, TVars>, "id" | "variables" | "returnPartialData"> {
client?: ApolloClient<any>;
// (undocumented)
from: StoreObject | Reference | string | null;
// (undocumented)
optimistic?: boolean;
}

// @public (undocumented)
export type UseSuspenseFragmentResult<TData> = {
data: MaybeMasked<TData>;
};

// @public (undocumented)
export function useSuspenseQuery<TData, TVariables extends OperationVariables, TOptions extends Omit<SuspenseQueryHookOptions<TData>, "variables">>(query: DocumentNode | TypedDocumentNode<TData, TVariables>, options?: SuspenseQueryHookOptions<NoInfer_2<TData>, NoInfer_2<TVariables>> & TOptions): UseSuspenseQueryResult<TOptions["errorPolicy"] extends "ignore" | "all" ? TOptions["returnPartialData"] extends true ? DeepPartial<TData> | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial<TData> | undefined : DeepPartial<TData> : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>;

Expand Down
34 changes: 34 additions & 0 deletions .api-reports/api-report-react_hooks.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2249,6 +2249,40 @@ export function useSubscription<TData = any, TVariables extends OperationVariabl
variables?: TVariables | undefined;
};

// Warning: (ae-forgotten-export) The symbol "UseSuspenseFragmentOptions" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {};
}): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: null;
}): UseSuspenseFragmentResult<null>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {} | null;
}): UseSuspenseFragmentResult<TData | null>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables>): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
interface UseSuspenseFragmentOptions<TData, TVars> extends Omit<Cache_2.DiffOptions<NoInfer<TData>, NoInfer<TVars>>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit<Cache_2.ReadFragmentOptions<TData, TVars>, "id" | "variables" | "returnPartialData"> {
client?: ApolloClient<any>;
// (undocumented)
from: StoreObject | Reference | string | null;
// (undocumented)
optimistic?: boolean;
}

// @public (undocumented)
export type UseSuspenseFragmentResult<TData> = {
data: MaybeMasked<TData>;
};

// Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookOptions" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
Expand Down
99 changes: 97 additions & 2 deletions .api-reports/api-report-react_internal.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,19 @@ interface FieldSpecifier {
variables?: Record<string, any>;
}

// @public (undocumented)
type FragmentCacheKey = [
cacheId: string,
fragment: DocumentNode,
stringifiedVariables: string
];

// @public (undocumented)
interface FragmentKey {
// (undocumented)
__fragmentKey?: string;
}

// @public
interface FragmentMap {
// (undocumented)
Expand All @@ -822,6 +835,41 @@ interface FragmentMap {
// @public (undocumented)
type FragmentMatcher = (rootValue: any, typeCondition: string, context: any) => boolean;

// @public (undocumented)
class FragmentReference<TData = unknown, TVariables = Record<string, unknown>> {
// Warning: (ae-forgotten-export) The symbol "FragmentReferenceOptions" needs to be exported by the entry point index.d.ts
constructor(client: ApolloClient<any>, watchFragmentOptions: WatchFragmentOptions<TData, TVariables> & {
from: string;
}, options: FragmentReferenceOptions);
// Warning: (ae-forgotten-export) The symbol "FragmentKey" needs to be exported by the entry point index.d.ts
//
// (undocumented)
readonly key: FragmentKey;
// Warning: (ae-forgotten-export) The symbol "Listener_2" needs to be exported by the entry point index.d.ts
//
// (undocumented)
listen(listener: Listener_2<MaybeMasked<TData>>): () => void;
// (undocumented)
readonly observable: Observable<WatchFragmentResult<TData>>;
// Warning: (ae-forgotten-export) The symbol "FragmentRefPromise" needs to be exported by the entry point index.d.ts
//
// (undocumented)
promise: FragmentRefPromise<MaybeMasked<TData>>;
// (undocumented)
retain(): () => void;
}

// @public (undocumented)
interface FragmentReferenceOptions {
// (undocumented)
onDispose?: () => void;
}

// Warning: (ae-forgotten-export) The symbol "PromiseWithState" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
type FragmentRefPromise<TData> = PromiseWithState<TData>;

// @public (undocumented)
type FragmentType<TData> = [
TData
Expand Down Expand Up @@ -1042,6 +1090,9 @@ type IsStrictlyAny<T> = UnionToIntersection<UnionForAny<T>> extends never ? true
// @public (undocumented)
type Listener<TData> = (promise: QueryRefPromise<TData>) => void;

// @public (undocumented)
type Listener_2<TData> = (promise: FragmentRefPromise<TData>) => void;

// @public (undocumented)
class LocalState<TCacheShape> {
// Warning: (ae-forgotten-export) The symbol "LocalStateOptions" needs to be exported by the entry point index.d.ts
Expand Down Expand Up @@ -1771,8 +1822,6 @@ export interface QueryReference<TData = unknown, TVariables = unknown> extends Q
toPromise?: unknown;
}

// Warning: (ae-forgotten-export) The symbol "PromiseWithState" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
type QueryRefPromise<TData> = PromiseWithState<ApolloQueryResult<MaybeMasked<TData>>>;

Expand Down Expand Up @@ -2004,6 +2053,13 @@ class SuspenseCache {
constructor(options?: SuspenseCacheOptions);
// (undocumented)
add(cacheKey: CacheKey, queryRef: InternalQueryReference<unknown>): void;
// Warning: (ae-forgotten-export) The symbol "FragmentCacheKey" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "FragmentReference" needs to be exported by the entry point index.d.ts
//
// (undocumented)
getFragmentRef<TData, TVariables>(cacheKey: FragmentCacheKey, client: ApolloClient<any>, options: WatchFragmentOptions<TData, TVariables> & {
from: string;
}): FragmentReference<TData, TVariables>;
// (undocumented)
getQueryRef<TData = any>(cacheKey: CacheKey, createObservable: () => ObservableQuery<TData>): InternalQueryReference<TData>;
}
Expand Down Expand Up @@ -2255,6 +2311,41 @@ interface UseReadQueryResult<TData = unknown> {
networkStatus: NetworkStatus;
}

// Warning: (ae-forgotten-export) The symbol "UseSuspenseFragmentOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "UseSuspenseFragmentResult" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {};
}): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: null;
}): UseSuspenseFragmentResult<null>;

// @public (undocumented)
function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {} | null;
}): UseSuspenseFragmentResult<TData | null>;

// @public (undocumented)
function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables>): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
interface UseSuspenseFragmentOptions<TData, TVars> extends Omit<Cache_2.DiffOptions<NoInfer<TData>, NoInfer<TVars>>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit<Cache_2.ReadFragmentOptions<TData, TVars>, "id" | "variables" | "returnPartialData"> {
client?: ApolloClient<any>;
// (undocumented)
from: StoreObject | Reference | string | null;
// (undocumented)
optimistic?: boolean;
}

// @public (undocumented)
type UseSuspenseFragmentResult<TData> = {
data: MaybeMasked<TData>;
};

// Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "UseSuspenseQueryResult" needs to be exported by the entry point index.d.ts
//
Expand Down Expand Up @@ -2382,6 +2473,10 @@ interface WrappableHooks {
//
// (undocumented)
useReadQuery: typeof useReadQuery;
// Warning: (ae-forgotten-export) The symbol "useSuspenseFragment" needs to be exported by the entry point index.d.ts
//
// (undocumented)
useSuspenseFragment: typeof useSuspenseFragment;
// Warning: (ae-forgotten-export) The symbol "useSuspenseQuery" needs to be exported by the entry point index.d.ts
//
// (undocumented)
Expand Down
34 changes: 34 additions & 0 deletions .api-reports/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3087,6 +3087,40 @@ export function useSubscription<TData = any, TVariables extends OperationVariabl
variables?: TVariables | undefined;
};

// Warning: (ae-forgotten-export) The symbol "UseSuspenseFragmentOptions" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {};
}): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: null;
}): UseSuspenseFragmentResult<null>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables> & {
from: {} | null;
}): UseSuspenseFragmentResult<TData | null>;

// @public (undocumented)
export function useSuspenseFragment<TData, TVariables extends OperationVariables>(options: UseSuspenseFragmentOptions<TData, TVariables>): UseSuspenseFragmentResult<TData>;

// @public (undocumented)
interface UseSuspenseFragmentOptions<TData, TVars> extends Omit<Cache_2.DiffOptions<NoInfer<TData>, NoInfer<TVars>>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit<Cache_2.ReadFragmentOptions<TData, TVars>, "id" | "variables" | "returnPartialData"> {
client?: ApolloClient<any>;
// (undocumented)
from: StoreObject | Reference | string | null;
// (undocumented)
optimistic?: boolean;
}

// @public (undocumented)
export type UseSuspenseFragmentResult<TData> = {
data: MaybeMasked<TData>;
};

// @public (undocumented)
export function useSuspenseQuery<TData, TVariables extends OperationVariables, TOptions extends Omit<SuspenseQueryHookOptions<TData>, "variables">>(query: DocumentNode | TypedDocumentNode<TData, TVariables>, options?: SuspenseQueryHookOptions<NoInfer_2<TData>, NoInfer_2<TVariables>> & TOptions): UseSuspenseQueryResult<TOptions["errorPolicy"] extends "ignore" | "all" ? TOptions["returnPartialData"] extends true ? DeepPartial<TData> | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial<TData> | undefined : DeepPartial<TData> : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>;

Expand Down
9 changes: 9 additions & 0 deletions .changeset/blue-comics-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@apollo/client": minor
---

Add a new `useSuspenseFragment` hook.

`useSuspenseFragment` suspends until `data` is complete. It is a drop-in
replacement for `useFragment` when you prefer to use Suspense to control the
loading state of a fragment.
2 changes: 1 addition & 1 deletion .size-limits.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"dist/apollo-client.min.cjs": 41615,
"dist/apollo-client.min.cjs": 42108,
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34349
}
1 change: 1 addition & 0 deletions config/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const react17TestFileIgnoreList = [
// We only support Suspense with React 18, so don't test suspense hooks with
// React 17
"src/testing/experimental/__tests__/createTestSchema.test.tsx",
"src/react/hooks/__tests__/useSuspenseFragment.test.tsx",
"src/react/hooks/__tests__/useSuspenseQuery.test.tsx",
"src/react/hooks/__tests__/useBackgroundQuery.test.tsx",
"src/react/hooks/__tests__/useLoadableQuery.test.tsx",
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/__snapshots__/exports.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Array [
"useReactiveVar",
"useReadQuery",
"useSubscription",
"useSuspenseFragment",
"useSuspenseQuery",
]
`;
Expand Down Expand Up @@ -293,6 +294,7 @@ Array [
"useReactiveVar",
"useReadQuery",
"useSubscription",
"useSuspenseFragment",
"useSuspenseQuery",
]
`;
Expand Down Expand Up @@ -338,6 +340,7 @@ Array [
"useReactiveVar",
"useReadQuery",
"useSubscription",
"useSuspenseFragment",
"useSuspenseQuery",
]
`;
Expand Down
Loading
Loading