Skip to content

Commit 22d48e4

Browse files
sserrataclaude
andauthored
fix: add React 19 / SSR compatibility guards (#1363)
- Schema: return null for empty oneOf/anyOf arrays instead of passing zero TabItems to SchemaTabs (which would throw) - SchemaTabs: filter null/undefined children and wrap sanitizeTabsChildren in try-catch; return null if no valid children survive sanitization - MethodEndpoint: early-return a static render during SSR to avoid useSelector running outside a Redux Provider Closes #1130. Partial credit to @stuartbuckell (PR #1257). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 68f9f66 commit 22d48e4

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/MethodEndpoint/index.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import React from "react";
99

1010
import BrowserOnly from "@docusaurus/BrowserOnly";
11+
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
1112
import { useTypedSelector } from "@theme/ApiItem/hooks";
1213

1314
function colorForMethod(method: string) {
@@ -38,6 +39,26 @@ export interface Props {
3839
}
3940

4041
function MethodEndpoint({ method, path, context }: Props) {
42+
// During SSR, render without Redux store access to avoid "Cannot read properties
43+
// of null (reading 'store')" errors caused by useSelector running outside a Provider.
44+
if (!ExecutionEnvironment.canUseDOM) {
45+
return (
46+
<>
47+
<pre className="openapi__method-endpoint">
48+
<span className={"badge badge--" + colorForMethod(method)}>
49+
{method === "event" ? "Webhook" : method.toUpperCase()}
50+
</span>{" "}
51+
{method !== "event" && (
52+
<h2 className="openapi__method-endpoint-path">
53+
{`${path.replace(/{([a-z0-9-_]+)}/gi, ":$1")}`}
54+
</h2>
55+
)}
56+
</pre>
57+
<div className="openapi__divider" />
58+
</>
59+
);
60+
}
61+
4162
let serverValue = useTypedSelector((state: any) => state.server.value);
4263
let serverUrlWithVariables = "";
4364

packages/docusaurus-theme-openapi-docs/src/theme/Schema/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,14 @@ const AnyOneOf: React.FC<SchemaProps> = ({
214214
schemaPath,
215215
}) => {
216216
const key = schema.oneOf ? "oneOf" : "anyOf";
217+
const schemaArray = schema[key];
218+
219+
// Empty oneOf/anyOf arrays are valid in OpenAPI specs but would cause the
220+
// Tabs component to throw "requires at least one TabItem". Return null instead.
221+
if (!schemaArray || !Array.isArray(schemaArray) || schemaArray.length === 0) {
222+
return null;
223+
}
224+
217225
const type = schema.oneOf
218226
? translate({ id: OPENAPI_SCHEMA_ITEM.ONE_OF, message: "oneOf" })
219227
: translate({ id: OPENAPI_SCHEMA_ITEM.ANY_OF, message: "anyOf" });

packages/docusaurus-theme-openapi-docs/src/theme/SchemaTabs/index.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,43 @@ function TabsComponent(props: SchemaTabsProps): React.JSX.Element {
223223
</div>
224224
);
225225
}
226-
export default function SchemaTabs(props: SchemaTabsProps): React.JSX.Element {
226+
export default function SchemaTabs(
227+
props: SchemaTabsProps
228+
): React.JSX.Element | null {
227229
const isBrowser = useIsBrowser();
230+
231+
const children = Array.isArray(props.children)
232+
? props.children.filter(Boolean)
233+
: props.children
234+
? [props.children]
235+
: [];
236+
237+
if (children.length === 0) {
238+
return null;
239+
}
240+
241+
let sanitizedChildren;
242+
try {
243+
sanitizedChildren = sanitizeTabsChildren(children);
244+
} catch {
245+
return null;
246+
}
247+
248+
if (
249+
!sanitizedChildren ||
250+
(Array.isArray(sanitizedChildren) && sanitizedChildren.length === 0)
251+
) {
252+
return null;
253+
}
254+
228255
return (
229256
<TabsComponent
230257
// Remount tabs after hydration
231258
// Temporary fix for https://github.com/facebook/docusaurus/issues/5653
232259
key={String(isBrowser)}
233260
{...props}
234261
>
235-
{sanitizeTabsChildren(props.children)}
262+
{sanitizedChildren}
236263
</TabsComponent>
237264
);
238265
}

0 commit comments

Comments
 (0)