From a4e16b9307345f9b6fb556fc13e33eca6e54cd80 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sun, 10 Apr 2022 14:24:34 +0800 Subject: [PATCH] use a separate dispatcher layer --- packages/docusaurus-types/src/index.d.ts | 4 -- packages/docusaurus/src/client/App.tsx | 2 +- .../src/client/ClientLifecyclesDispatcher.tsx | 66 +++++++++++++++++++ .../src/client/PendingNavigation.tsx | 47 +++++-------- .../src/client/clientLifecyclesDispatcher.ts | 37 ----------- website/_dogfooding/clientModuleExample.ts | 18 ++--- 6 files changed, 89 insertions(+), 85 deletions(-) create mode 100644 packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx delete mode 100644 packages/docusaurus/src/client/clientLifecyclesDispatcher.ts diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index beb887b885a5..348a3ded439e 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -599,10 +599,6 @@ export type ClientModule = { previousLocation: Location | null; location: Location; }) => void; - onRouteDidUpdate?: (args: { - previousLocation: Location | null; - location: Location; - }) => void; onRouteUpdateDelayed?: (args: {location: Location}) => void; }; diff --git a/packages/docusaurus/src/client/App.tsx b/packages/docusaurus/src/client/App.tsx index ca367d6bce3f..a50b716f9678 100644 --- a/packages/docusaurus/src/client/App.tsx +++ b/packages/docusaurus/src/client/App.tsx @@ -19,7 +19,7 @@ import SiteMetadataDefaults from './SiteMetadataDefaults'; import Root from '@theme/Root'; import SiteMetadata from '@theme/SiteMetadata'; -import './clientLifecyclesDispatcher'; +import '@generated/client-modules'; // TODO, quick fix for CSS insertion order import ErrorBoundary from '@docusaurus/ErrorBoundary'; diff --git a/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx b/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx new file mode 100644 index 000000000000..e426038c5483 --- /dev/null +++ b/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx @@ -0,0 +1,66 @@ +/** + * 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, { + useEffect, + useImperativeHandle, + useLayoutEffect, + type ReactNode, +} from 'react'; +import clientModules from '@generated/client-modules'; +import type {ClientModule} from '@docusaurus/types'; +import type {Location} from 'history'; + +function dispatchLifecycleAction( + lifecycleAction: K, + ...args: Parameters> +) { + clientModules.forEach((clientModule) => { + const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ?? + clientModule[lifecycleAction]) as + | ((...a: Parameters>) => void) + | undefined; + + lifecycleFunction?.(...args); + }); +} + +function ClientLifecyclesDispatcher( + { + children, + location, + previousLocation, + }: { + children: ReactNode; + location: Location; + previousLocation: Location | null; + }, + ref: React.ForwardedRef, +): JSX.Element { + useEffect(() => { + if (previousLocation !== location) { + const {hash} = location; + if (!hash) { + window.scrollTo(0, 0); + } else { + const id = decodeURIComponent(hash.substring(1)); + const element = document.getElementById(id); + element?.scrollIntoView(); + } + } + }); + useLayoutEffect(() => { + dispatchLifecycleAction('onRouteUpdate', {previousLocation, location}); + }); + useImperativeHandle(ref, () => ({ + onRouteUpdateDelayed: () => + dispatchLifecycleAction('onRouteUpdateDelayed', {location}), + })); + return <>{children}; +} + +export default React.forwardRef(ClientLifecyclesDispatcher); diff --git a/packages/docusaurus/src/client/PendingNavigation.tsx b/packages/docusaurus/src/client/PendingNavigation.tsx index 5d3db10f03a4..bb89c2697cef 100644 --- a/packages/docusaurus/src/client/PendingNavigation.tsx +++ b/packages/docusaurus/src/client/PendingNavigation.tsx @@ -9,9 +9,10 @@ import React from 'react'; import {Route} from 'react-router-dom'; import nprogress from 'nprogress'; -import clientLifecyclesDispatcher from './clientLifecyclesDispatcher'; +import ClientLifecyclesDispatcher from './ClientLifecyclesDispatcher'; import preload from './preload'; import type {Location} from 'history'; +import type {ClientModule} from '@docusaurus/types'; import './nprogress.css'; @@ -29,6 +30,7 @@ type State = { class PendingNavigation extends React.Component { private previousLocation: Location | null; private progressBarTimeout: number | null; + private clientLifecyclesDispatcher: React.RefObject>; constructor(props: Props) { super(props); @@ -36,6 +38,7 @@ class PendingNavigation extends React.Component { // previousLocation doesn't affect rendering, hence not stored in state. this.previousLocation = null; this.progressBarTimeout = null; + this.clientLifecyclesDispatcher = React.createRef(); this.state = { nextRouteHasLoaded: true, }; @@ -60,38 +63,13 @@ class PendingNavigation extends React.Component { // Load data while the old screen remains. preload(nextLocation.pathname) - .then(() => { - if (this.previousLocation?.pathname !== nextLocation.pathname) { - clientLifecyclesDispatcher.onRouteUpdate({ - previousLocation: this.previousLocation, - location: nextLocation, - }); - } - this.setState({nextRouteHasLoaded: true}, this.stopProgressBar); - }) + .then(() => + this.setState({nextRouteHasLoaded: true}, this.stopProgressBar), + ) .catch((e) => console.warn(e)); return false; } - override componentDidUpdate(): void { - if (this.previousLocation !== this.props.location) { - const {hash} = this.props.location; - if (!hash) { - window.scrollTo(0, 0); - } else { - const id = decodeURIComponent(hash.substring(1)); - const element = document.getElementById(id); - element?.scrollIntoView(); - } - } - if (this.previousLocation?.pathname !== this.props.location.pathname) { - clientLifecyclesDispatcher.onRouteDidUpdate({ - previousLocation: this.previousLocation, - location: this.props.location, - }); - } - } - private clearProgressBarTimeout() { if (this.progressBarTimeout) { window.clearTimeout(this.progressBarTimeout); @@ -102,7 +80,7 @@ class PendingNavigation extends React.Component { private startProgressBar() { this.clearProgressBarTimeout(); this.progressBarTimeout = window.setTimeout(() => { - clientLifecyclesDispatcher.onRouteUpdateDelayed({ + this.clientLifecyclesDispatcher.current!.onRouteUpdateDelayed({ location: this.props.location, }); nprogress.start(); @@ -118,7 +96,14 @@ class PendingNavigation extends React.Component { const {children, location} = this.props; // Use a controlled to trick all descendants into rendering the old // location. - return children} />; + return ( + + children} /> + + ); } } diff --git a/packages/docusaurus/src/client/clientLifecyclesDispatcher.ts b/packages/docusaurus/src/client/clientLifecyclesDispatcher.ts deleted file mode 100644 index a90373cd5c39..000000000000 --- a/packages/docusaurus/src/client/clientLifecyclesDispatcher.ts +++ /dev/null @@ -1,37 +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 clientModules from '@generated/client-modules'; -import type {ClientModule} from '@docusaurus/types'; - -function dispatchLifecycleAction( - lifecycleAction: K, - args: Parameters>, -) { - clientModules.forEach((clientModule) => { - const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ?? - clientModule[lifecycleAction]) as - | ((...a: Parameters>) => void) - | undefined; - - lifecycleFunction?.(...args); - }); -} - -const clientLifecyclesDispatchers: Required = { - onRouteUpdate(...args) { - dispatchLifecycleAction('onRouteUpdate', args); - }, - onRouteDidUpdate(...args) { - dispatchLifecycleAction('onRouteDidUpdate', args); - }, - onRouteUpdateDelayed(...args) { - dispatchLifecycleAction('onRouteUpdateDelayed', args); - }, -}; - -export default clientLifecyclesDispatchers; diff --git a/website/_dogfooding/clientModuleExample.ts b/website/_dogfooding/clientModuleExample.ts index 94981e6581d0..81634b53c618 100644 --- a/website/_dogfooding/clientModuleExample.ts +++ b/website/_dogfooding/clientModuleExample.ts @@ -13,25 +13,19 @@ export function onRouteUpdate({ previousLocation, }: { location: Location; - previousLocation: Location; + previousLocation: Location | null; }): void { if (ExecutionEnvironment.canUseDOM) { - console.log(`onRouteUpdate (Fired before DOM repaints) -Previous location: ${previousLocation.pathname} + console.log(`onRouteUpdate +Previous location: ${previousLocation?.pathname} Current location: ${location.pathname} Current heading: ${document.getElementsByTagName('h1')[0]?.innerText}`); } } -export function onRouteDidUpdate({ - location, - previousLocation, -}: { - location: Location; - previousLocation: Location; -}): void { + +export function onRouteUpdateDelayed({location}: {location: Location}): void { if (ExecutionEnvironment.canUseDOM) { - console.log(`onRouteDidUpdate (Fired after DOM repaints) -Previous location: ${previousLocation.pathname} + console.log(`onRouteUpdateDelayed Current location: ${location.pathname} Current heading: ${document.getElementsByTagName('h1')[0]?.innerText}`); }