diff --git a/.changeset/brave-buttons-grab.md b/.changeset/brave-buttons-grab.md new file mode 100644 index 00000000000..18c136161ed --- /dev/null +++ b/.changeset/brave-buttons-grab.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Deprecate `useFragment` `returnPartialData` option diff --git a/src/react/hooks/__tests__/useFragment.test.tsx b/src/react/hooks/__tests__/useFragment.test.tsx index dab0f5efcb5..5e7f8ed5adf 100644 --- a/src/react/hooks/__tests__/useFragment.test.tsx +++ b/src/react/hooks/__tests__/useFragment.test.tsx @@ -911,4 +911,94 @@ describe("useFragment", () => { ]); }); }); + + describe("tests with incomplete data", () => { + let cache: InMemoryCache, wrapper: React.FunctionComponent; + const ItemFragment = gql` + fragment ItemFragment on Item { + id + text + } + `; + + beforeEach(() => { + cache = new InMemoryCache(); + + wrapper = ({ children }: any) => {children}; + + // silence the console for the incomplete fragment write + const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); + cache.writeFragment({ + fragment: ItemFragment, + data: { + __typename: "Item", + id: 5, + }, + }); + spy.mockRestore(); + }); + + it("assumes `returnPartialData: true` per default", () => { + const { result } = renderHook( + () => + useFragment({ + fragment: ItemFragment, + from: { __typename: "Item", id: 5 }, + }), + { wrapper } + ); + + expect(result.current.data).toEqual({ __typename: "Item", id: 5 }); + expect(result.current.complete).toBe(false); + }); + + it("throws an exception with `returnPartialData: false` if only partial data is available", () => { + // this is actually not intended behavior, but it is the current behavior + // let's document it in a test until we remove `returnPartialData` in 3.8 + + let error: Error; + + renderHook( + () => { + // we can't just `expect(() => renderHook(...)).toThrow(...)` because it will render a second time, resulting in an uncaught exception + try { + useFragment({ + fragment: ItemFragment, + from: { __typename: "Item", id: 5 }, + returnPartialData: false, + }); + } catch (e) { + error = e; + } + }, + { wrapper } + ); + + expect(error!.toString()).toMatch(`Error: Can't find field 'text' on Item:5 object`); + }); + + it("throws an exception with `returnPartialData: false` if no data is available", () => { + // this is actually not intended behavior, but it is the current behavior + // let's document it in a test until we remove `returnPartialData` in 3.8 + let error: Error; + + renderHook( + () => { + // we can't just `expect(() => renderHook(...)).toThrow(...)` because it will render a second time, resulting in an uncaught exception + try { + useFragment({ + fragment: ItemFragment, + from: { __typename: "Item", id: 6 }, + returnPartialData: false, + }); + } catch (e) { + error = e; + } + }, + { wrapper } + ); + + expect(error!.toString()).toMatch(`Error: Dangling reference to missing Item:6 object`); + }); + }); }); diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index 187a83222aa..5bf0a282958 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -20,13 +20,22 @@ extends Omit< | "query" | "optimistic" | "previousResult" ->, Omit< - Cache.ReadFragmentOptions, + | "returnPartialData" +>, Omit, | "id" + | "returnPartialData" > { from: StoreObject | Reference | string; // Override this field to make it optional (default: true). optimistic?: boolean; + + /** + * Whether to return incomplete data rather than null. + * Defaults to `true`. + * @deprecated This option will be removed in Apollo Client 3.8. + * Please check `result.missing` instead. + */ + returnPartialData?: boolean; } // Since the above definition of UseFragmentOptions can be hard to parse without