refactor(theme): move LayoutProviders to Layout/Provider; composeProviders util (#7676)

Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
Sébastien Lorber 2022-06-24 18:36:27 +02:00 committed by GitHub
parent 90a8ca387e
commit afc08eef4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 44 deletions

View file

@ -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' {

View file

@ -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 <Provider>{children}</Provider>;
}

View file

@ -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 (
<LayoutProviders>
<LayoutProvider>
<PageMetadata title={title} description={description} />
<SkipToContent />
@ -53,6 +53,6 @@ export default function Layout(props: Props): JSX.Element {
</div>
{!noFooter && <Footer />}
</LayoutProviders>
</LayoutProvider>
);
}

View file

@ -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 (
<ColorModeProvider>
<AnnouncementBarProvider>
<TabGroupChoiceProvider>
<ScrollControllerProvider>
<DocsPreferredVersionContextProvider>
<PluginHtmlClassNameProvider>
<NavbarProvider>{children}</NavbarProvider>
</PluginHtmlClassNameProvider>
</DocsPreferredVersionContextProvider>
</ScrollControllerProvider>
</TabGroupChoiceProvider>
</AnnouncementBarProvider>
</ColorModeProvider>
);
}

View file

@ -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({
</DocsPreferredVersionContextProviderUnsafe>
);
}
return children;
return <>{children}</>;
}
function useDocsPreferredVersionContext(): ContextValue {

View file

@ -40,6 +40,7 @@ export {
useIsomorphicLayoutEffect,
useEvent,
usePrevious,
composeProviders,
ReactContextError,
} from './utils/reactUtils';

View file

@ -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<O extends object>(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) => (
<CurrentProvider>{element}</CurrentProvider>
),
children,
)}
</>
);
}