Skip to content

Commit

Permalink
Allow null as valid from value in useFragment
Browse files Browse the repository at this point in the history
  • Loading branch information
jerelmiller committed Nov 14, 2024
1 parent 03a6171 commit 7e4e261
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 20 deletions.
36 changes: 35 additions & 1 deletion src/react/hooks/__tests__/useFragment.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,40 @@ describe("useFragment", () => {
}
});

it("allows `null` as valid `from` value without warning", async () => {
using _ = spyOnConsole("warn");

interface Fragment {
age: number;
}

const fragment: TypedDocumentNode<Fragment, never> = gql`
fragment UserFields on User {
age
}
`;

const client = new ApolloClient({ cache: new InMemoryCache() });

const { takeSnapshot } = renderHookToSnapshotStream(
() => useFragment({ fragment, from: null }),
{
wrapper: ({ children }) => (
<ApolloProvider client={client}>{children}</ApolloProvider>
),
}
);

{
const { data, complete } = await takeSnapshot();

expect(data).toEqual({});
expect(complete).toBe(false);
}

expect(console.warn).not.toHaveBeenCalled();
});

describe("tests with incomplete data", () => {
let cache: InMemoryCache, wrapper: React.FunctionComponent;
const ItemFragment = gql`
Expand Down Expand Up @@ -2327,7 +2361,7 @@ describe.skip("Type Tests", () => {

test("UseFragmentOptions interface shape", <TData, TVars>() => {
expectTypeOf<UseFragmentOptions<TData, TVars>>().branded.toEqualTypeOf<{
from: string | StoreObject | Reference | FragmentType<TData>;
from: string | StoreObject | Reference | FragmentType<TData> | null;
fragment: DocumentNode | TypedDocumentNode<TData, TVars>;
fragmentName?: string;
optimistic?: boolean;
Expand Down
55 changes: 36 additions & 19 deletions src/react/hooks/useFragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface UseFragmentOptions<TData, TVars>
Cache.ReadFragmentOptions<TData, TVars>,
"id" | "variables" | "returnPartialData"
> {
from: StoreObject | Reference | FragmentType<NoInfer<TData>> | string;
from: StoreObject | Reference | FragmentType<NoInfer<TData>> | string | null;
// Override this field to make it optional (default: true).
optimistic?: boolean;
/**
Expand Down Expand Up @@ -73,7 +73,10 @@ function _useFragment<TData = any, TVars = OperationVariables>(
// `stableOptions` and retrigger our subscription. If the cache identifier
// stays the same between renders, we want to reuse the existing subscription.
const id = React.useMemo(
() => (typeof from === "string" ? from : cache.identify(from)),
() =>
typeof from === "string" ? from
: from === null ? null
: cache.identify(from),
[cache, from]
);

Expand All @@ -83,6 +86,16 @@ function _useFragment<TData = any, TVars = OperationVariables>(
// get the correct diff on the next render given new diffOptions
const diff = React.useMemo(() => {
const { fragment, fragmentName, from, optimistic = true } = stableOptions;

if (from === null) {
return {
result: diffToResult({
result: {} as TData,
complete: false,
}),
};
}

const { cache } = client;
const diff = cache.diff<TData>({
...stableOptions,
Expand Down Expand Up @@ -111,24 +124,28 @@ function _useFragment<TData = any, TVars = OperationVariables>(
React.useCallback(
(forceUpdate) => {
let lastTimeout = 0;
const subscription = client.watchFragment(stableOptions).subscribe({
next: (result) => {
// Since `next` is called async by zen-observable, we want to avoid
// unnecessarily rerendering this hook for the initial result
// emitted from watchFragment which should be equal to
// `diff.result`.
if (equal(result, diff.result)) return;
diff.result = result;
// If we get another update before we've re-rendered, bail out of
// the update and try again. This ensures that the relative timing
// between useQuery and useFragment stays roughly the same as
// fixed in https://github.com/apollographql/apollo-client/pull/11083
clearTimeout(lastTimeout);
lastTimeout = setTimeout(forceUpdate) as any;
},
});

const subscription =
stableOptions.from === null ?
null
: client.watchFragment(stableOptions).subscribe({
next: (result) => {
// Since `next` is called async by zen-observable, we want to avoid
// unnecessarily rerendering this hook for the initial result
// emitted from watchFragment which should be equal to
// `diff.result`.
if (equal(result, diff.result)) return;
diff.result = result;
// If we get another update before we've re-rendered, bail out of
// the update and try again. This ensures that the relative timing
// between useQuery and useFragment stays roughly the same as
// fixed in https://github.com/apollographql/apollo-client/pull/11083
clearTimeout(lastTimeout);
lastTimeout = setTimeout(forceUpdate) as any;
},
});
return () => {
subscription.unsubscribe();
subscription?.unsubscribe();
clearTimeout(lastTimeout);
};
},
Expand Down

0 comments on commit 7e4e261

Please sign in to comment.