Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/preact-deferred-fragments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/preact': minor
---

Add a beta Preact `useFragment` hook that masks fragment data and suspends while streamed `@defer` selections are incomplete. Deferred tracking happens in `@urql/core`'s `Client`, so Suspense boundaries resolve from later query results without any binding wiring, and outside of Suspense the hook updates through `makeFragmentSource` when deferred patches arrive.
1 change: 1 addition & 0 deletions packages/preact-urql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"preact": ">= 10.0.0"
},
"dependencies": {
"@0no-co/graphql.web": "^1.0.13",
"@urql/core": "workspace:^6.0.3",
"wonka": "^6.3.2"
},
Expand Down
68 changes: 68 additions & 0 deletions packages/preact-urql/src/hooks/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { FragmentDefinitionNode } from '@0no-co/graphql.web';
import type { Client } from '@urql/core';

/** A pending suspense {@link Promise} that the `useFragment` hook throws.
*
* @internal
*/
export type FragmentPromise = Promise<unknown> & {
_resolve: () => void;
};

type FragmentCache = WeakMap<
FragmentDefinitionNode,
WeakMap<object, FragmentPromise>
>;

interface ClientWithCache extends Client {
_fragments?: FragmentCache;
}

/** Returns a pending fragment promise for this exact fragment and data object.
*
* @remarks
* Weak keys keep sibling objects and named fragments independent and allow
* abandoned suspended renders to be garbage-collected without an effect.
*
* @internal
*/
export const getFragmentPromise = (
client: Client,
fragment: FragmentDefinitionNode,
data: object
): FragmentPromise | undefined => {
const cache = (client as ClientWithCache)._fragments;
const entries = cache && cache.get(fragment);
return entries && entries.get(data);
};

/** Stores a pending promise for this exact fragment and data object.
*
* @internal
*/
export const setFragmentPromise = (
client: Client,
fragment: FragmentDefinitionNode,
data: object,
promise: FragmentPromise
): void => {
const target = client as ClientWithCache;
const cache = target._fragments || (target._fragments = new WeakMap());
let entries = cache.get(fragment);
if (!entries) cache.set(fragment, (entries = new WeakMap()));
entries.set(data, promise);
};

/** Deletes a settled fragment promise.
*
* @internal
*/
export const deleteFragmentPromise = (
client: Client,
fragment: FragmentDefinitionNode,
data: object
): void => {
const cache = (client as ClientWithCache)._fragments;
const entries = cache && cache.get(fragment);
if (entries) entries.delete(data);
};
1 change: 1 addition & 0 deletions packages/preact-urql/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './useFragment';
export * from './useQuery';
export * from './useMutation';
export * from './useSubscription';
Loading
Loading