Skip to content

Instantly share code, notes, and snippets.

@tarunsahnan
Last active April 28, 2025 21:13
Show Gist options
  • Save tarunsahnan/93418e81882f2e343e09894bc6de6f35 to your computer and use it in GitHub Desktop.
Save tarunsahnan/93418e81882f2e343e09894bc6de6f35 to your computer and use it in GitHub Desktop.
This is a fix for "Jest does not support rendering nested async components." For more details, refer to my comment: https://github.com/testing-library/react-testing-library/issues/1209#issuecomment-2692563090
// While using [this solution](https://github.com/testing-library/react-testing-library/issues/1209#issuecomment-2400054404),
// I encountered a timeout issue caused by API calls in my components. Each component was waiting for its
// API call to complete before rendering, which led to delays and affected the flow to subsequent components.
// To resolve this, I refactored the logic so that the API calls are initiated in parallel.
// Then, the function waits for all components to finish rendering.
import { render } from "@testing-library/react";
import React, {
Children,
cloneElement,
isValidElement,
ReactElement,
ReactNode,
} from "react";
function setFakeReactDispatcher<T>(action: () => T): T {
/**
* We use some internals from React to avoid a lot of warnings in our tests when faking
* to render server components. If the structure of React changes, this function should still work,
* but the tests will again print warnings.
*
* If this is the case, this function can also simply be removed and all tests should still function.
*/
if (!("__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED" in React)) {
return action();
}
const secret = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
if (
!secret ||
typeof secret !== "object" ||
!("ReactCurrentDispatcher" in secret)
) {
return action();
}
const currentDispatcher = secret.ReactCurrentDispatcher;
if (
!currentDispatcher ||
typeof currentDispatcher !== "object" ||
!("current" in currentDispatcher)
) {
return action();
}
const previousDispatcher = currentDispatcher.current;
try {
currentDispatcher.current = new Proxy(
{},
{
get() {
throw new Error("This is a client component");
},
},
);
} catch {
return action();
}
const result = action();
currentDispatcher.current = previousDispatcher;
return result;
}
async function evaluateServerComponent(
node: ReactElement,
): Promise<ReactElement> {
if (node && node.type?.constructor?.name === "AsyncFunction") {
// Handle async server nodes by calling await.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const evaluatedNode: ReactElement = await node.type({ ...node.props });
return evaluateServerComponent(evaluatedNode);
}
if (node && node.type?.constructor?.name === "Function") {
try {
return setFakeReactDispatcher(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const evaluatedNode: ReactElement = node.type({ ...node.props });
return evaluateServerComponent(evaluatedNode);
});
} catch {
// If evaluating fails with a function node, it might be because of using client side hooks.
// In that case, simply return the node, it will be handled by the react testing library render function.
return node;
}
}
return node;
}
async function evaluateServerComponentAndChildren(node: ReactElement) {
const evaluatedNode = await evaluateServerComponent(node);
if (!evaluatedNode?.props.children) {
return evaluatedNode;
}
const children = Children.toArray(evaluatedNode.props.children);
const promises = [];
for (let i = 0; i < children.length; i += 1) {
const child = children[i];
if (!isValidElement(child)) {
continue;
}
promises.push({
index: i,
promise: new Promise(async (resolve) => {
const value = await evaluateServerComponentAndChildren(child);
resolve({ index: i, value });
}),
});
}
const values = (await Promise.all(promises.map((c) => c.promise))) as {
index: number;
value: React.ReactElement;
}[];
values.forEach((v) => {
children[v.index] = v.value;
});
return cloneElement(evaluatedNode, {}, ...children);
}
// Follow <https://github.com/testing-library/react-testing-library/issues/1209>
// for the latest updates on React Testing Library support for React Server
// Components (RSC)
export async function renderServerComponent(
nodeOrPromise: ReactNode | Promise<ReactNode>,
) {
// @ts-expect-error
const node = await nodeOrPromise;
if (isValidElement(node)) {
const evaluatedNode = await evaluateServerComponentAndChildren(node);
return render(evaluatedNode);
}
return render(node);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment