Skip to content

Commit

Permalink
Fix issue with useFragment where it returned wrong data when changi…
Browse files Browse the repository at this point in the history
…ng the `from` option (#11689)
  • Loading branch information
jerelmiller committed Mar 18, 2024
1 parent 2b0820c commit cb8ffe5
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-singers-kiss.md
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Fix issue where passing a new `from` option to `useFragment` would first render with the previous value before rerendering with the correct value.
2 changes: 1 addition & 1 deletion .size-limits.json
@@ -1,4 +1,4 @@
{
"dist/apollo-client.min.cjs": 39248,
"dist/apollo-client.min.cjs": 39267,
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32630
}
57 changes: 56 additions & 1 deletion src/react/hooks/__tests__/useFragment.test.tsx
Expand Up @@ -29,7 +29,7 @@ import { concatPagination } from "../../../utilities";
import assert from "assert";
import { expectTypeOf } from "expect-type";
import { SubscriptionObserver } from "zen-observable-ts";
import { profile, spyOnConsole } from "../../../testing/internal";
import { profile, profileHook, spyOnConsole } from "../../../testing/internal";

describe("useFragment", () => {
it("is importable and callable", () => {
Expand Down Expand Up @@ -1359,6 +1359,61 @@ describe("useFragment", () => {
});
});

it("returns correct data when options change", async () => {
const client = new ApolloClient({
cache: new InMemoryCache(),
});
type User = { __typename: "User"; id: number; name: string };
const fragment: TypedDocumentNode<User> = gql`
fragment UserFragment on User {
id
name
}
`;

client.writeFragment({
fragment,
data: { __typename: "User", id: 1, name: "Alice" },
});

client.writeFragment({
fragment,
data: { __typename: "User", id: 2, name: "Charlie" },
});

const ProfiledHook = profileHook(({ id }: { id: number }) =>
useFragment({ fragment, from: { __typename: "User", id } })
);

const { rerender } = render(<ProfiledHook id={1} />, {
wrapper: ({ children }) => (
<ApolloProvider client={client}>{children}</ApolloProvider>
),
});

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot).toEqual({
complete: true,
data: { __typename: "User", id: 1, name: "Alice" },
});
}

rerender(<ProfiledHook id={2} />);

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot).toEqual({
complete: true,
data: { __typename: "User", id: 2, name: "Charlie" },
});
}

await expect(ProfiledHook).not.toRerender();
});

describe("tests with incomplete data", () => {
let cache: InMemoryCache, wrapper: React.FunctionComponent;
const ItemFragment = gql`
Expand Down
6 changes: 6 additions & 0 deletions src/react/hooks/useFragment.ts
Expand Up @@ -88,6 +88,12 @@ function _useFragment<TData = any, TVars = OperationVariables>(
diffToResult(cache.diff<TData>(diffOptions))
);

// Since .next is async, we need to make sure that we
// get the correct diff on the next render given new diffOptions
React.useMemo(() => {
resultRef.current = diffToResult(cache.diff<TData>(diffOptions));
}, [diffOptions, cache]);

// Used for both getSnapshot and getServerSnapshot
const getSnapshot = React.useCallback(() => resultRef.current, []);

Expand Down

0 comments on commit cb8ffe5

Please sign in to comment.