diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
index f248ab38a0..494579be0d 100644
--- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts
+++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
@@ -596,14 +596,14 @@ declare module '@theme/Layout' {
export default function Layout(props: Props): JSX.Element;
}
-declare module '@theme/LayoutProviders' {
+declare module '@theme/Layout/Provider' {
import type {ReactNode} from 'react';
export interface Props {
readonly children: ReactNode;
}
- export default function LayoutProviders(props: Props): JSX.Element;
+ export default function LayoutProvider(props: Props): JSX.Element;
}
declare module '@theme/SearchMetadata' {
diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/Provider/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/Provider/index.tsx
new file mode 100644
index 0000000000..4a6d316bdf
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/Layout/Provider/index.tsx
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import {composeProviders} from '@docusaurus/theme-common';
+import {
+ ColorModeProvider,
+ TabGroupChoiceProvider,
+ AnnouncementBarProvider,
+ DocsPreferredVersionContextProvider,
+ ScrollControllerProvider,
+ NavbarProvider,
+ PluginHtmlClassNameProvider,
+} from '@docusaurus/theme-common/internal';
+import type {Props} from '@theme/Layout/Provider';
+
+const Provider = composeProviders([
+ ColorModeProvider,
+ AnnouncementBarProvider,
+ TabGroupChoiceProvider,
+ ScrollControllerProvider,
+ DocsPreferredVersionContextProvider,
+ PluginHtmlClassNameProvider,
+ NavbarProvider,
+]);
+
+export default function LayoutProvider({children}: Props): JSX.Element {
+ return {children};
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx
index 262edf2c14..26308f7eb2 100644
--- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx
@@ -14,7 +14,7 @@ import SkipToContent from '@theme/SkipToContent';
import AnnouncementBar from '@theme/AnnouncementBar';
import Navbar from '@theme/Navbar';
import Footer from '@theme/Footer';
-import LayoutProviders from '@theme/LayoutProviders';
+import LayoutProvider from '@theme/Layout/Provider';
import ErrorPageContent from '@theme/ErrorPageContent';
import type {Props} from '@theme/Layout';
import styles from './styles.module.css';
@@ -32,7 +32,7 @@ export default function Layout(props: Props): JSX.Element {
useKeyboardNavigation();
return (
-
+
@@ -53,6 +53,6 @@ export default function Layout(props: Props): JSX.Element {
{!noFooter && }
-
+
);
}
diff --git a/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx b/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx
deleted file mode 100644
index 063646f93a..0000000000
--- a/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import React from 'react';
-import {
- ColorModeProvider,
- TabGroupChoiceProvider,
- AnnouncementBarProvider,
- DocsPreferredVersionContextProvider,
- ScrollControllerProvider,
- NavbarProvider,
- PluginHtmlClassNameProvider,
-} from '@docusaurus/theme-common/internal';
-import type {Props} from '@theme/LayoutProviders';
-
-export default function LayoutProviders({children}: Props): JSX.Element {
- return (
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
- );
-}
diff --git a/packages/docusaurus-theme-common/src/contexts/docsPreferredVersion.tsx b/packages/docusaurus-theme-common/src/contexts/docsPreferredVersion.tsx
index 96e941143f..908b4854c9 100644
--- a/packages/docusaurus-theme-common/src/contexts/docsPreferredVersion.tsx
+++ b/packages/docusaurus-theme-common/src/contexts/docsPreferredVersion.tsx
@@ -175,7 +175,7 @@ function DocsPreferredVersionContextProviderUnsafe({
export function DocsPreferredVersionContextProvider({
children,
}: {
- children: JSX.Element;
+ children: ReactNode;
}): JSX.Element {
if (isDocsPluginEnabled) {
return (
@@ -184,7 +184,7 @@ export function DocsPreferredVersionContextProvider({
);
}
- return children;
+ return <>{children}>;
}
function useDocsPreferredVersionContext(): ContextValue {
diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts
index e5e1c36dd3..9cc56897fe 100644
--- a/packages/docusaurus-theme-common/src/index.ts
+++ b/packages/docusaurus-theme-common/src/index.ts
@@ -40,6 +40,7 @@ export {
useIsomorphicLayoutEffect,
useEvent,
usePrevious,
+ composeProviders,
ReactContextError,
} from './utils/reactUtils';
diff --git a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx
index 1c069adab2..95ea937909 100644
--- a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx
@@ -5,7 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
-import {useCallback, useEffect, useLayoutEffect, useMemo, useRef} from 'react';
+import React, {
+ useCallback,
+ useEffect,
+ useLayoutEffect,
+ useMemo,
+ useRef,
+ type ComponentType,
+ type ReactNode,
+} from 'react';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
/**
@@ -90,3 +98,32 @@ export function useShallowMemoObject(obj: O): O {
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(() => obj, deps.flat());
}
+
+type SimpleProvider = ComponentType<{children: ReactNode}>;
+
+/**
+ * Creates a single React provider from an array of existing providers
+ * assuming providers only take "children" as props.
+ *
+ * Prevents the annoying React element nesting
+ * Example here: https://getfrontend.tips/compose-multiple-react-providers/
+ *
+ * The order matters:
+ * - The first provider is at the top of the tree.
+ * - The last provider is the most nested one
+ *
+ * @param providers array of providers to compose
+ */
+export function composeProviders(providers: SimpleProvider[]): SimpleProvider {
+ // Creates a single React component: it's cheaper to compose JSX elements
+ return ({children}) => (
+ <>
+ {providers.reduceRight(
+ (element, CurrentProvider) => (
+ {element}
+ ),
+ children,
+ )}
+ >
+ );
+}