feat(v2): 🔥 🔥 better loading UX ️ (#1383)

* feat(v2): render old screen while loading

* replace and remove stuff

* spacing

* add spinner back, just as a fallback

* nits

* Turn off loading spinner on top right

* address review
This commit is contained in:
Endilie Yacop Sucipto 2019-04-24 01:08:50 +07:00 committed by GitHub
parent 23e56f61f5
commit 10908009e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 24 deletions

View file

@ -7,13 +7,13 @@
import React, {useState} from 'react';
import {renderRoutes} from 'react-router-config';
import ReactListenerProvider from 'react-listener-provider';
import Head from '@docusaurus/Head'; // eslint-disable-line
import routes from '@generated/routes'; // eslint-disable-line
import metadata from '@generated/metadata'; // eslint-disable-line
import siteConfig from '@generated/docusaurus.config'; //eslint-disable-line
import DocusaurusContext from '@docusaurus/context'; // eslint-disable-line
import PendingNavigation from './PendingNavigation';
function App() {
const [context, setContext] = useState({});
@ -36,7 +36,9 @@ function App() {
type="text/css"
/>
</Head>
<ReactListenerProvider>{renderRoutes(routes)}</ReactListenerProvider>
<PendingNavigation routes={routes}>
{renderRoutes(routes)}
</PendingNavigation>
</DocusaurusContext.Provider>
);
}

View file

@ -0,0 +1,84 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* 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 {Route, withRouter} from 'react-router-dom';
import nprogress from 'nprogress';
import 'nprogress/nprogress.css';
import preload from './preload';
nprogress.configure({showSpinner: false});
class PendingNavigation extends React.Component {
constructor(props) {
super(props);
this.progressBarTimeout = null;
this.state = {
previousLocation: null,
};
}
componentWillReceiveProps(nextProps) {
const navigated = nextProps.location !== this.props.location;
const {routes, delay = 1000} = this.props;
if (navigated) {
window.scrollTo(0, 0);
this.startProgressBar(delay);
// save the location so we can render the old screen
this.setState({
previousLocation: this.props.location,
});
// load data while the old screen remains
preload(routes, nextProps.location.pathname)
.then(() => {
this.setState(
{
previousLocation: null,
},
this.stopProgressBar,
);
})
.catch(e => console.warn(e));
}
}
clearProgressBarTimeout() {
if (this.progressBarTimeout) {
clearTimeout(this.progressBarTimeout);
this.progressBarTimeout = null;
}
}
startProgressBar(delay) {
this.clearProgressBarTimeout();
this.progressBarTimeout = setTimeout(() => {
nprogress.start();
}, delay);
}
stopProgressBar() {
this.clearProgressBarTimeout();
nprogress.done();
}
render() {
const {children, location} = this.props;
const {previousLocation} = this.state;
// use a controlled <Route> to trick all descendants into
// rendering the old location
return (
<Route location={previousLocation || location} render={() => children} />
);
}
}
export default withRouter(PendingNavigation);

View file

@ -6,15 +6,15 @@
*/
import React, {useEffect} from 'react';
import Perimeter from 'react-perimeter';
import {NavLink} from 'react-router-dom';
const internalRegex = /^\/(?!\/)/;
function Link(props) {
const {to, href, preloadProximity = 20} = props;
const {to, href} = props;
const targetLink = to || href;
const isInternal = internalRegex.test(targetLink);
let preloaded = false;
const IOSupported =
typeof window !== 'undefined' && 'IntersectionObserver' in window;
@ -47,6 +47,13 @@ function Link(props) {
}
};
const onMouseEnter = () => {
if (!preloaded) {
window.docusaurus.preload(targetLink);
preloaded = true;
}
};
useEffect(() => {
// If IO is not supported. We prefetch by default (only once)
if (!IOSupported && isInternal) {
@ -64,12 +71,12 @@ function Link(props) {
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a {...props} href={targetLink} />
) : (
<Perimeter
padding={preloadProximity}
onBreach={() => window.docusaurus.preload(targetLink)}
once>
<NavLink {...props} innerRef={handleRef} to={targetLink} />
</Perimeter>
<NavLink
{...props}
onMouseEnter={onMouseEnter}
innerRef={handleRef}
to={targetLink}
/>
);
}

View file

@ -55,13 +55,12 @@
"is-wsl": "^1.1.0",
"lodash": "^4.17.11",
"mini-css-extract-plugin": "^0.4.1",
"nprogress": "^0.2.0",
"portfinder": "^1.0.13",
"react-dev-utils": "^8.0.0",
"react-helmet": "^6.0.0-beta",
"react-listener-provider": "^0.2.0",
"react-loadable": "^5.5.0",
"react-loadable-ssr-addon": "^0.1.6",
"react-perimeter": "^0.4.0",
"react-router-config": "^5.0.0",
"react-router-dom": "^5.0.0",
"semver": "^6.0.0",

View file

@ -9509,6 +9509,11 @@ npm-which@^3.0.1:
gauge "~2.7.3"
set-blocking "~2.0.0"
nprogress@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E=
nth-check@^1.0.2, nth-check@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
@ -10986,11 +10991,6 @@ react-is@^16.8.1, react-is@^16.8.4:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.5.tgz#c54ac229dd66b5afe0de5acbe47647c3da692ff8"
integrity sha512-sudt2uq5P/2TznPV4Wtdi+Lnq3yaYW8LfvPKLM9BKD8jJNBkxMVyB0C9/GmVhLw7Jbdmndk/73n7XQGeN9A3QQ==
react-listener-provider@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/react-listener-provider/-/react-listener-provider-0.2.0.tgz#fb6ce123f9e20ad948f75906ddf647db1df31f18"
integrity sha1-+2zhI/niCtlI91kG3fZH2x3zHxg=
react-loadable-ssr-addon@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon/-/react-loadable-ssr-addon-0.1.6.tgz#de3e45f2b3876dc7b9afae19138370f0588b7300"
@ -11003,13 +11003,6 @@ react-loadable@^5.5.0:
dependencies:
prop-types "^15.5.0"
react-perimeter@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/react-perimeter/-/react-perimeter-0.4.0.tgz#be86b5560bb96650272aaf7bd37639460b82645b"
integrity sha512-hSjth5oh4L/ZVR6PfR9eu7B1lil0AteGXLn7ki7YNJI7bkKlZf28pwFyFKNIXkoRJT3FiD01kGYkAmIz1k8b9A==
optionalDependencies:
react-listener-provider "^0.2.0"
react-router-config@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.0.0.tgz#3d7e298dc64479bf9e1cc77080b8778e9a8d966c"