Skip to content

Commit

Permalink
Make useRenderGuard more resilient to changes in React internals. (#…
Browse files Browse the repository at this point in the history
…11659)

* Make `useRenderGuard` more resilient to changes in React internals.

* uhhh... confusing, but okay
  • Loading branch information
phryneas committed Mar 11, 2024
1 parent 564954a commit 652a61e
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-pens-kick.md
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Make `useRenderGuard` more resilient to changes in React internals.
2 changes: 1 addition & 1 deletion .size-limits.json
@@ -1,4 +1,4 @@
{
"dist/apollo-client.min.cjs": 39245,
"dist/apollo-client.min.cjs": 39247,
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32630
}
76 changes: 76 additions & 0 deletions src/react/hooks/internal/__tests__/useRenderGuard.test.tsx
@@ -0,0 +1,76 @@
/* eslint-disable testing-library/render-result-naming-convention */
import React, { useEffect } from "react";
import { useRenderGuard } from "../useRenderGuard";
import { render, waitFor } from "@testing-library/react";
import { withCleanup } from "../../../../testing/internal";

const UNDEF = {};

it("returns a function that returns `true` if called during render", () => {
let result: boolean | typeof UNDEF = UNDEF;
function TestComponent() {
const calledDuringRender = useRenderGuard();
result = calledDuringRender();
return <>Test</>;
}
render(<TestComponent />);
expect(result).toBe(true);
});

it("returns a function that returns `false` if called after render", async () => {
let result: boolean | typeof UNDEF = UNDEF;
function TestComponent() {
const calledDuringRender = useRenderGuard();
useEffect(() => {
result = calledDuringRender();
});
return <>Test</>;
}
render(<TestComponent />);
await waitFor(() => {
expect(result).not.toBe(UNDEF);
});
expect(result).toBe(false);
});

function breakReactInternalsTemporarily() {
const R = React as unknown as {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: any;
};
const orig = R.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;

R.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {};
return withCleanup({}, () => {
R.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = orig;
});
}

it("results in false negatives if React internals change", () => {
let result: boolean | typeof UNDEF = UNDEF;
function TestComponent() {
using _ = breakReactInternalsTemporarily();
const calledDuringRender = useRenderGuard();
result = calledDuringRender();
return <>Test</>;
}
render(<TestComponent />);
expect(result).toBe(false);
});

it("does not result in false positives if React internals change", async () => {
let result: boolean | typeof UNDEF = UNDEF;
function TestComponent() {
using _ = breakReactInternalsTemporarily();
const calledDuringRender = useRenderGuard();
useEffect(() => {
using _ = breakReactInternalsTemporarily();
result = calledDuringRender();
});
return <>Test</>;
}
render(<TestComponent />);
await waitFor(() => {
expect(result).not.toBe(UNDEF);
});
expect(result).toBe(false);
});
2 changes: 1 addition & 1 deletion src/react/hooks/internal/useRenderGuard.ts
Expand Up @@ -16,7 +16,7 @@ export function useRenderGuard() {

return React.useCallback(() => {
return (
RenderDispatcher !== null && RenderDispatcher === getRenderDispatcher()
RenderDispatcher != null && RenderDispatcher === getRenderDispatcher()
);
}, []);
}

0 comments on commit 652a61e

Please sign in to comment.