Skip to content

Commit

Permalink
use a separate dispatcher layer
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Cena committed Apr 10, 2022
1 parent d71d643 commit a4e16b9
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 85 deletions.
4 changes: 0 additions & 4 deletions packages/docusaurus-types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus/src/client/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
66 changes: 66 additions & 0 deletions packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx
Original file line number Diff line number Diff line change
@@ -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<K extends keyof ClientModule>(
lifecycleAction: K,
...args: Parameters<NonNullable<ClientModule[K]>>
) {
clientModules.forEach((clientModule) => {
const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ??
clientModule[lifecycleAction]) as
| ((...a: Parameters<NonNullable<ClientModule[K]>>) => void)
| undefined;

lifecycleFunction?.(...args);
});
}

function ClientLifecyclesDispatcher(
{
children,
location,
previousLocation,
}: {
children: ReactNode;
location: Location;
previousLocation: Location | null;
},
ref: React.ForwardedRef<ClientModule>,
): 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);
47 changes: 16 additions & 31 deletions packages/docusaurus/src/client/PendingNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -29,13 +30,15 @@ type State = {
class PendingNavigation extends React.Component<Props, State> {
private previousLocation: Location | null;
private progressBarTimeout: number | null;
private clientLifecyclesDispatcher: React.RefObject<Required<ClientModule>>;

constructor(props: Props) {
super(props);

// previousLocation doesn't affect rendering, hence not stored in state.
this.previousLocation = null;
this.progressBarTimeout = null;
this.clientLifecyclesDispatcher = React.createRef();
this.state = {
nextRouteHasLoaded: true,
};
Expand All @@ -60,38 +63,13 @@ class PendingNavigation extends React.Component<Props, State> {

// 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);
Expand All @@ -102,7 +80,7 @@ class PendingNavigation extends React.Component<Props, State> {
private startProgressBar() {
this.clearProgressBarTimeout();
this.progressBarTimeout = window.setTimeout(() => {
clientLifecyclesDispatcher.onRouteUpdateDelayed({
this.clientLifecyclesDispatcher.current!.onRouteUpdateDelayed({
location: this.props.location,
});
nprogress.start();
Expand All @@ -118,7 +96,14 @@ class PendingNavigation extends React.Component<Props, State> {
const {children, location} = this.props;
// Use a controlled <Route> to trick all descendants into rendering the old
// location.
return <Route location={location} render={() => children} />;
return (
<ClientLifecyclesDispatcher
ref={this.clientLifecyclesDispatcher}
previousLocation={this.previousLocation}
location={location}>
<Route location={location} render={() => children} />
</ClientLifecyclesDispatcher>
);
}
}

Expand Down
37 changes: 0 additions & 37 deletions packages/docusaurus/src/client/clientLifecyclesDispatcher.ts

This file was deleted.

18 changes: 6 additions & 12 deletions website/_dogfooding/clientModuleExample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
Expand Down

0 comments on commit a4e16b9

Please sign in to comment.