mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-06 12:52:31 +02:00
refactor(core): replace useDocusaurusContext().isClient by useIsBrowser() (#5349)
* extract separate useIsClient() hook * for consistency, rename to `useIsBrowser` * useless return * improve doc for BrowserOnly * update snapshot * polish
This commit is contained in:
parent
69b11a8546
commit
295e77cc09
20 changed files with 213 additions and 90 deletions
|
@ -179,6 +179,10 @@ declare module '@docusaurus/useDocusaurusContext' {
|
|||
export default function useDocusaurusContext(): DocusaurusContext;
|
||||
}
|
||||
|
||||
declare module '@docusaurus/useIsBrowser' {
|
||||
export default function useIsBrowser(): boolean;
|
||||
}
|
||||
|
||||
declare module '@docusaurus/useBaseUrl' {
|
||||
export type BaseUrlOptions = {
|
||||
forcePrependBaseUrl?: boolean;
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import useThemeContext from '@theme/hooks/useThemeContext';
|
||||
import type {Props} from '@theme/ThemedImage';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const ThemedImage = (props: Props): JSX.Element => {
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const isBrowser = useIsBrowser();
|
||||
const {isDarkTheme} = useThemeContext();
|
||||
const {sources, className, alt = '', ...propsRest} = props;
|
||||
|
||||
|
@ -23,7 +23,7 @@ const ThemedImage = (props: Props): JSX.Element => {
|
|||
|
||||
const clientThemes: SourceName[] = isDarkTheme ? ['dark'] : ['light'];
|
||||
|
||||
const renderedSourceNames: SourceName[] = isClient
|
||||
const renderedSourceNames: SourceName[] = isBrowser
|
||||
? clientThemes
|
||||
: // We need to render both images on the server to avoid flash
|
||||
// See https://github.com/facebook/docusaurus/pull/3730
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import React, {ReactNode, useState, useCallback} from 'react';
|
||||
import {MDXProvider} from '@mdx-js/react';
|
||||
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import renderRoutes from '@docusaurus/renderRoutes';
|
||||
import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs-types';
|
||||
import Layout from '@theme/Layout';
|
||||
|
@ -37,7 +36,6 @@ function DocPageContent({
|
|||
versionMetadata,
|
||||
children,
|
||||
}: DocPageContentProps): JSX.Element {
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const {pluginId, version} = versionMetadata;
|
||||
|
||||
const sidebarName = currentDocRoute.sidebar;
|
||||
|
@ -57,7 +55,6 @@ function DocPageContent({
|
|||
|
||||
return (
|
||||
<Layout
|
||||
key={`${isClient}`} // TODO seems suspicious
|
||||
wrapperClassName={ThemeClassNames.wrapper.docPages}
|
||||
pageClassName={ThemeClassNames.page.docPage}
|
||||
searchMetadatas={{
|
||||
|
|
|
@ -17,7 +17,6 @@ import {useThemeConfig} from '@docusaurus/theme-common';
|
|||
const Logo = (props: Props): JSX.Element => {
|
||||
const {
|
||||
siteConfig: {title},
|
||||
isClient,
|
||||
} = useDocusaurusContext();
|
||||
const {
|
||||
navbar: {title: navbarTitle, logo = {src: ''}},
|
||||
|
@ -37,7 +36,6 @@ const Logo = (props: Props): JSX.Element => {
|
|||
{...(logo.target && {target: logo.target})}>
|
||||
{logo.src && (
|
||||
<ThemedImage
|
||||
key={`${isClient}`} // TODO seems suspicious
|
||||
className={imageClassName}
|
||||
sources={sources}
|
||||
alt={logo.alt || navbarTitle || title}
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import useThemeContext from '@theme/hooks/useThemeContext';
|
||||
import type {Props} from '@theme/ThemedImage';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const ThemedImage = (props: Props): JSX.Element => {
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const isBrowser = useIsBrowser();
|
||||
const {isDarkTheme} = useThemeContext();
|
||||
const {sources, className, alt = '', ...propsRest} = props;
|
||||
|
||||
|
@ -23,7 +23,7 @@ const ThemedImage = (props: Props): JSX.Element => {
|
|||
|
||||
const clientThemes: SourceName[] = isDarkTheme ? ['dark'] : ['light'];
|
||||
|
||||
const renderedSourceNames: SourceName[] = isClient
|
||||
const renderedSourceNames: SourceName[] = isBrowser
|
||||
? clientThemes
|
||||
: // We need to render both images on the server to avoid flash
|
||||
// See https://github.com/facebook/docusaurus/pull/3730
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, {useState, useRef, memo, CSSProperties} from 'react';
|
||||
import type {Props} from '@theme/Toggle';
|
||||
import {useThemeConfig} from '@docusaurus/theme-common';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import './styles.css';
|
||||
|
@ -91,11 +91,11 @@ export default function (props: Props): JSX.Element {
|
|||
switchConfig: {darkIcon, darkIconStyle, lightIcon, lightIconStyle},
|
||||
},
|
||||
} = useThemeConfig();
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const isBrowser = useIsBrowser();
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
disabled={!isClient}
|
||||
disabled={!isBrowser}
|
||||
icons={{
|
||||
checked: <Dark icon={darkIcon} style={darkIconStyle} />,
|
||||
unchecked: <Light icon={lightIcon} style={lightIconStyle} />,
|
||||
|
|
|
@ -47,10 +47,6 @@ function useWindowSize(): WindowSize {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!ExecutionEnvironment.canUseDOM) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function updateWindowSize() {
|
||||
setWindowSize(getWindowSize());
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, {ComponentProps, ReactElement, useRef, useState} from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import clsx from 'clsx';
|
||||
import {useCollapsible, Collapsible} from '../Collapsible';
|
||||
import styles from './styles.module.css';
|
||||
|
@ -30,7 +30,7 @@ export type DetailsProps = {
|
|||
} & ComponentProps<'details'>;
|
||||
|
||||
const Details = ({summary, children, ...props}: DetailsProps): JSX.Element => {
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const isBrowser = useIsBrowser();
|
||||
const detailsRef = useRef<HTMLDetailsElement>(null);
|
||||
|
||||
const {collapsed, setCollapsed} = useCollapsible({
|
||||
|
@ -48,7 +48,7 @@ const Details = ({summary, children, ...props}: DetailsProps): JSX.Element => {
|
|||
data-collapsed={collapsed}
|
||||
className={clsx(
|
||||
styles.details,
|
||||
{[styles.isClient]: isClient},
|
||||
{[styles.isBrowser]: isBrowser},
|
||||
props.className,
|
||||
)}>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
|
||||
|
|
|
@ -46,9 +46,9 @@ CSS variables, meant to be overriden by final theme
|
|||
}
|
||||
|
||||
/* When JS disabled/failed to load: we use the open property for arrow animation: */
|
||||
.details[open]:not(.isClient) > summary:before,
|
||||
.details[open]:not(.isBrowser) > summary:before,
|
||||
/* When JS works: we use the data-attribute for arrow animation */
|
||||
.details[data-collapsed='false'].isClient > summary:before {
|
||||
.details[data-collapsed='false'].isBrowser > summary:before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import React, {
|
|||
useContext,
|
||||
createContext,
|
||||
} from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import {createStorageSlot} from './storageUtils';
|
||||
import {useThemeConfig} from './useThemeConfig';
|
||||
|
||||
|
@ -39,10 +39,10 @@ type AnnouncementBarAPI = {
|
|||
|
||||
const useAnnouncementBarContextValue = (): AnnouncementBarAPI => {
|
||||
const {announcementBar} = useThemeConfig();
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const isBrowser = useIsBrowser();
|
||||
|
||||
const [isClosed, setClosed] = useState(() => {
|
||||
return isClient
|
||||
return isBrowser
|
||||
? // On client navigation: init with localstorage value
|
||||
isDismissedInStorage()
|
||||
: // On server/hydration: always visible to prevent layout shifts (will be hidden with css if needed)
|
||||
|
|
|
@ -10,6 +10,7 @@ import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live';
|
|||
import clsx from 'clsx';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import usePrismTheme from '@theme/hooks/usePrismTheme';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
|
@ -51,8 +52,8 @@ function EditorWithHeader() {
|
|||
}
|
||||
|
||||
export default function Playground({children, transformCode, ...props}) {
|
||||
const isBrowser = useIsBrowser();
|
||||
const {
|
||||
isClient,
|
||||
siteConfig: {
|
||||
themeConfig: {
|
||||
liveCodeBlock: {playgroundPosition},
|
||||
|
@ -64,8 +65,8 @@ export default function Playground({children, transformCode, ...props}) {
|
|||
return (
|
||||
<div className={styles.playgroundContainer}>
|
||||
<LiveProvider
|
||||
key={isClient}
|
||||
code={isClient ? children.replace(/\n$/, '') : ''}
|
||||
key={isBrowser}
|
||||
code={isBrowser ? children.replace(/\n$/, '') : ''}
|
||||
transformCode={transformCode || ((code) => `${code};`)}
|
||||
theme={prismTheme}
|
||||
{...props}>
|
||||
|
|
5
packages/docusaurus-types/src/index.d.ts
vendored
5
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -125,7 +125,10 @@ export interface DocusaurusContext {
|
|||
globalData: Record<string, unknown>;
|
||||
i18n: I18n;
|
||||
codeTranslations: Record<string, string>;
|
||||
isClient: boolean;
|
||||
|
||||
// Don't put mutable values here, to avoid triggering re-renders
|
||||
// We could reconsider that choice if context selectors are implemented
|
||||
// isBrowser: boolean; // Not here on purpose!
|
||||
}
|
||||
|
||||
export interface Preset {
|
||||
|
|
|
@ -5,16 +5,12 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import routes from '@generated/routes';
|
||||
import siteConfig from '@generated/docusaurus.config';
|
||||
import globalData from '@generated/globalData';
|
||||
import i18n from '@generated/i18n';
|
||||
import codeTranslations from '@generated/codeTranslations';
|
||||
import siteMetadata from '@generated/site-metadata';
|
||||
import renderRoutes from './exports/renderRoutes';
|
||||
import DocusaurusContext from './exports/context';
|
||||
import {BrowserContextProvider} from './exports/browserContext';
|
||||
import {DocusaurusContextProvider} from './exports/docusaurusContext';
|
||||
import PendingNavigation from './PendingNavigation';
|
||||
import BaseUrlIssueBanner from './baseUrlIssueBanner/BaseUrlIssueBanner';
|
||||
import Root from '@theme/Root';
|
||||
|
@ -22,29 +18,17 @@ import Root from '@theme/Root';
|
|||
import './client-lifecycles-dispatcher';
|
||||
|
||||
function App(): JSX.Element {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DocusaurusContext.Provider
|
||||
value={{
|
||||
siteConfig,
|
||||
siteMetadata,
|
||||
globalData,
|
||||
i18n,
|
||||
codeTranslations,
|
||||
isClient,
|
||||
}}>
|
||||
<Root>
|
||||
<BaseUrlIssueBanner />
|
||||
<PendingNavigation routes={routes}>
|
||||
{renderRoutes(routes)}
|
||||
</PendingNavigation>
|
||||
</Root>
|
||||
</DocusaurusContext.Provider>
|
||||
<DocusaurusContextProvider>
|
||||
<BrowserContextProvider>
|
||||
<Root>
|
||||
<BaseUrlIssueBanner />
|
||||
<PendingNavigation routes={routes}>
|
||||
{renderRoutes(routes)}
|
||||
</PendingNavigation>
|
||||
</Root>
|
||||
</BrowserContextProvider>
|
||||
</DocusaurusContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
|
||||
// Similar comp to the one described here:
|
||||
// https://www.joshwcomeau.com/react/the-perils-of-rehydration/#abstractions
|
||||
function BrowserOnly({
|
||||
children,
|
||||
fallback,
|
||||
|
@ -15,9 +17,9 @@ function BrowserOnly({
|
|||
children?: () => JSX.Element;
|
||||
fallback?: JSX.Element;
|
||||
}): JSX.Element | null {
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const isBrowser = useIsBrowser();
|
||||
|
||||
if (isClient && children != null) {
|
||||
if (isBrowser && children != null) {
|
||||
return <>{children()}</>;
|
||||
}
|
||||
|
||||
|
|
32
packages/docusaurus/src/client/exports/browserContext.tsx
Normal file
32
packages/docusaurus/src/client/exports/browserContext.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* 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, {ReactNode, useEffect, useState} from 'react';
|
||||
|
||||
// Encapsulate the logic to avoid React hydration problems
|
||||
// See https://www.joshwcomeau.com/react/the-perils-of-rehydration/
|
||||
// On first client-side render, we need to render exactly as the server rendered
|
||||
// isBrowser is set to true only after a successful hydration
|
||||
|
||||
// Note, isBrowser is not part of useDocusaurusContext() for perf reasons
|
||||
// Using useDocusaurusContext() (much more common need) should not trigger re-rendering after a successful hydration
|
||||
|
||||
export const Context = React.createContext<boolean>(false);
|
||||
|
||||
export function BrowserContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
const [isBrowser, setIsBrowser] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsBrowser(true);
|
||||
}, []);
|
||||
|
||||
return <Context.Provider value={isBrowser}>{children}</Context.Provider>;
|
||||
}
|
35
packages/docusaurus/src/client/exports/docusaurusContext.tsx
Normal file
35
packages/docusaurus/src/client/exports/docusaurusContext.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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, {ReactNode} from 'react';
|
||||
import {DocusaurusContext} from '@docusaurus/types';
|
||||
|
||||
import siteConfig from '@generated/docusaurus.config';
|
||||
import globalData from '@generated/globalData';
|
||||
import i18n from '@generated/i18n';
|
||||
import codeTranslations from '@generated/codeTranslations';
|
||||
import siteMetadata from '@generated/site-metadata';
|
||||
|
||||
// Static value on purpose: don't make it dynamic!
|
||||
// Using context is still useful for testability reasons.
|
||||
const contextValue: DocusaurusContext = {
|
||||
siteConfig,
|
||||
siteMetadata,
|
||||
globalData,
|
||||
i18n,
|
||||
codeTranslations,
|
||||
};
|
||||
|
||||
export const Context = React.createContext<DocusaurusContext>(contextValue);
|
||||
|
||||
export function DocusaurusContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
|
||||
}
|
|
@ -6,16 +6,11 @@
|
|||
*/
|
||||
|
||||
import {useContext} from 'react';
|
||||
import context from './context';
|
||||
import {Context} from './docusaurusContext';
|
||||
import {DocusaurusContext} from '@docusaurus/types';
|
||||
|
||||
function useDocusaurusContext(): DocusaurusContext {
|
||||
const docusaurusContext = useContext(context);
|
||||
if (docusaurusContext === null) {
|
||||
// should not happen normally
|
||||
throw new Error('Docusaurus context not provided.');
|
||||
}
|
||||
return docusaurusContext;
|
||||
return useContext(Context);
|
||||
}
|
||||
|
||||
export default useDocusaurusContext;
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {DocusaurusContext} from '@docusaurus/types';
|
||||
import {useContext} from 'react';
|
||||
import {Context} from './browserContext';
|
||||
|
||||
export default React.createContext<DocusaurusContext | null>(null);
|
||||
export default function useIsBrowser(): boolean {
|
||||
return useContext(Context);
|
||||
}
|
|
@ -10,14 +10,16 @@ Object {
|
|||
"@docusaurus/Link": "../../../../client/exports/Link.tsx",
|
||||
"@docusaurus/Noop": "../../../../client/exports/Noop.ts",
|
||||
"@docusaurus/Translate": "../../../../client/exports/Translate.tsx",
|
||||
"@docusaurus/browserContext": "../../../../client/exports/browserContext.tsx",
|
||||
"@docusaurus/constants": "../../../../client/exports/constants.ts",
|
||||
"@docusaurus/context": "../../../../client/exports/context.ts",
|
||||
"@docusaurus/docusaurusContext": "../../../../client/exports/docusaurusContext.tsx",
|
||||
"@docusaurus/isInternalUrl": "../../../../client/exports/isInternalUrl.ts",
|
||||
"@docusaurus/renderRoutes": "../../../../client/exports/renderRoutes.ts",
|
||||
"@docusaurus/router": "../../../../client/exports/router.ts",
|
||||
"@docusaurus/useBaseUrl": "../../../../client/exports/useBaseUrl.ts",
|
||||
"@docusaurus/useDocusaurusContext": "../../../../client/exports/useDocusaurusContext.ts",
|
||||
"@docusaurus/useGlobalData": "../../../../client/exports/useGlobalData.ts",
|
||||
"@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts",
|
||||
"@generated": "../../../../../../..",
|
||||
"@site": "",
|
||||
"@theme-init/PluginThemeComponentOverridden": "pluginThemeFolder/PluginThemeComponentOverridden.js",
|
||||
|
@ -51,13 +53,15 @@ Object {
|
|||
"@docusaurus/Link": "../../client/exports/Link.tsx",
|
||||
"@docusaurus/Noop": "../../client/exports/Noop.ts",
|
||||
"@docusaurus/Translate": "../../client/exports/Translate.tsx",
|
||||
"@docusaurus/browserContext": "../../client/exports/browserContext.tsx",
|
||||
"@docusaurus/constants": "../../client/exports/constants.ts",
|
||||
"@docusaurus/context": "../../client/exports/context.ts",
|
||||
"@docusaurus/docusaurusContext": "../../client/exports/docusaurusContext.tsx",
|
||||
"@docusaurus/isInternalUrl": "../../client/exports/isInternalUrl.ts",
|
||||
"@docusaurus/renderRoutes": "../../client/exports/renderRoutes.ts",
|
||||
"@docusaurus/router": "../../client/exports/router.ts",
|
||||
"@docusaurus/useBaseUrl": "../../client/exports/useBaseUrl.ts",
|
||||
"@docusaurus/useDocusaurusContext": "../../client/exports/useDocusaurusContext.ts",
|
||||
"@docusaurus/useGlobalData": "../../client/exports/useGlobalData.ts",
|
||||
"@docusaurus/useIsBrowser": "../../client/exports/useIsBrowser.ts",
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -109,19 +109,56 @@ const Home = () => {
|
|||
|
||||
### `<BrowserOnly/>` {#browseronly}
|
||||
|
||||
The `<BrowserOnly>` component accepts a `children` prop, a render function which will not be executed during the pre-rendering phase of the build process. This is useful for hiding code that is only meant to run in the browsers (e.g. where the `window`/`document` objects are being accessed). To improve SEO, you can also provide fallback content using the `fallback` prop, which will be prerendered until in the build process and replaced with the client-side only contents when viewed in the browser.
|
||||
The `<BrowserOnly>` component permits to render React components only in the browser, after the React app has hydrated.
|
||||
|
||||
```jsx {1,5-10}
|
||||
:::tip
|
||||
|
||||
Use it for integrating with code that can't run in Node.js, because `window` or `document` objects are being accessed.
|
||||
|
||||
:::
|
||||
|
||||
#### Props {#browseronly-props}
|
||||
|
||||
- `children`: render function prop returning browser-only JSX. Will not be executed in Node.js
|
||||
- `fallback` (optional): JSX to render on the server (Node.js) and until React hydration completes.
|
||||
|
||||
#### Example with code {#browseronly-example-code}
|
||||
|
||||
```jsx
|
||||
// highlight-start
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
// highlight-end
|
||||
|
||||
const MyComponent = () => {
|
||||
return (
|
||||
<BrowserOnly
|
||||
fallback={<div>The fallback content to display on prerendering</div>}>
|
||||
// highlight-start
|
||||
<BrowserOnly>
|
||||
{() => {
|
||||
// Something that should be excluded during build process prerendering.
|
||||
<span>page url = {window.location.href}</span>;
|
||||
}}
|
||||
</BrowserOnly>
|
||||
// highlight-end
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Example with a library {#browseronly-example-library}
|
||||
|
||||
```jsx
|
||||
// highlight-start
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
// highlight-end
|
||||
|
||||
const MyComponent = (props) => {
|
||||
return (
|
||||
// highlight-start
|
||||
<BrowserOnly fallback={<div>Loading...</div>}>
|
||||
{() => {
|
||||
const LibComponent = require('some-lib').LibComponent;
|
||||
return <LibComponent {...props} />;
|
||||
}}
|
||||
</BrowserOnly>
|
||||
// highlight-end
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -132,7 +169,7 @@ A simple interpolation component for text containing dynamic placeholders.
|
|||
|
||||
The placeholders will be replaced with the provided dynamic values and JSX elements of your choice (strings, links, styled elements...).
|
||||
|
||||
#### Props {#props}
|
||||
#### Props {#interpolate-props}
|
||||
|
||||
- `children`: text containing interpolation placeholders like `{placeholderName}`
|
||||
- `values`: object containing interpolation placeholder values
|
||||
|
@ -175,7 +212,7 @@ Apart the `values` prop used for interpolation, it is **not possible to use vari
|
|||
|
||||
:::
|
||||
|
||||
#### Props {#props-1}
|
||||
#### Props {#translate-props}
|
||||
|
||||
- `children`: untranslated string in the default site locale (can contain [interpolation placeholders](#interpolate))
|
||||
- `id`: optional value to use as key in JSON translation files
|
||||
|
@ -253,7 +290,6 @@ interface DocusaurusContext {
|
|||
globalData: Record<string, unknown>;
|
||||
i18n: I18n;
|
||||
codeTranslations: Record<string, string>;
|
||||
isClient: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -275,6 +311,34 @@ const MyComponent = () => {
|
|||
};
|
||||
```
|
||||
|
||||
### `useIsBrowser` {#useIsBrowser}
|
||||
|
||||
Returns `true` when the React app has successfully hydrated in the browser.
|
||||
|
||||
:::caution
|
||||
|
||||
Use this hook instead of `typeof windows !== 'undefined'` in React rendering logic.
|
||||
|
||||
The first client-side render output (in the browser) **must be exactly the same** as the server-side render output (Node.js).
|
||||
|
||||
Not following this rule can lead to unexpected hydration behaviors, as described in [The Perils of Rehydration](https://www.joshwcomeau.com/react/the-perils-of-rehydration/).
|
||||
|
||||
:::
|
||||
|
||||
Usage example:
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
|
||||
const MyComponent = () => {
|
||||
// highlight-start
|
||||
const isBrowser = useIsBrowser();
|
||||
// highlight-end
|
||||
return <div>{isBrowser ? 'Client' : 'Server'}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### `useBaseUrl` {#usebaseurl}
|
||||
|
||||
React hook to prepend your site `baseUrl` to a string.
|
||||
|
@ -525,21 +589,27 @@ export default function Home() {
|
|||
|
||||
### `ExecutionEnvironment` {#executionenvironment}
|
||||
|
||||
A module which exposes a few boolean variables to check the current rendering environment. Useful if you want to only run certain code on client/server or need to write server-side rendering compatible code.
|
||||
A module which exposes a few boolean variables to check the current rendering environment.
|
||||
|
||||
```jsx {2,5}
|
||||
import React from 'react';
|
||||
:::caution
|
||||
|
||||
For React rendering logic, use [`useIsBrowser()`](#useIsBrowser) or [`<BrowserOnly>`](#browseronly) instead.
|
||||
|
||||
:::
|
||||
|
||||
Example:
|
||||
|
||||
```jsx
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
|
||||
const MyPage = () => {
|
||||
const location = ExecutionEnvironment.canUseDOM ? window.location.href : null;
|
||||
return <div>{location}</div>;
|
||||
};
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
require('lib-that-only-works-client-side');
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `ExecutionEnvironment.canUseDOM` | `true` if on client, `false` if prerendering. |
|
||||
| `ExecutionEnvironment.canUseDOM` | `true` if on client/browser, `false` on Node.js/prerendering. |
|
||||
| `ExecutionEnvironment.canUseEventListeners` | `true` if on client and has `window.addEventListener`. |
|
||||
| `ExecutionEnvironment.canUseIntersectionObserver` | `true` if on client and has `IntersectionObserver`. |
|
||||
| `ExecutionEnvironment.canUseViewport` | `true` if on client and has `window.screen`. |
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue