diff --git a/src/components/AsyncComponentLoader/Component.js b/src/components/AsyncComponentLoader/Component.js
deleted file mode 100644
index 69018ef..0000000
--- a/src/components/AsyncComponentLoader/Component.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import React, { Suspense } from 'react';
-
-import Loading from 'components/Loading';
-
-const AsyncComponentLoader = (Component, loadingProps) => props => {
- return (
- }>
-
-
- );
-};
-
-export default AsyncComponentLoader;
diff --git a/src/components/AsyncComponentLoader/index.js b/src/components/AsyncComponentLoader/index.js
deleted file mode 100644
index bf109b8..0000000
--- a/src/components/AsyncComponentLoader/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import Component from './Component';
-
-export default Component;
diff --git a/src/components/Page/styles.js b/src/components/Page/styles.js
index 65935c4..7af1bb8 100644
--- a/src/components/Page/styles.js
+++ b/src/components/Page/styles.js
@@ -6,6 +6,7 @@ const useStyles = makeStyles(theme => ({
root: {
height: '100%',
overflow: 'hidden',
+ position: 'relative',
'padding-left': theme.spacing(isMobile ? 1.5 : 3),
'padding-right': theme.spacing(isMobile ? 1.5 : 3),
},
diff --git a/src/config/index.js b/src/config/index.js
index 7195333..2abb408 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -81,6 +81,12 @@ const notifications = {
maxSnack: isMobile ? 3 : 4,
};
+const loader = {
+ // no more blinking in your app
+ delay: 300, // if your asynchronous process is finished during 300 milliseconds you will not see the loader at all
+ minimumLoading: 700, // but if it appears, it will stay for at least 700 milliseconds
+};
+
export {
messages,
cancelationMessage,
@@ -89,6 +95,7 @@ export {
email,
domain,
repository,
+ loader,
title,
themePair,
notifications,
diff --git a/src/routes/index.js b/src/routes/index.js
index ee6af32..fb0036e 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -1,35 +1,33 @@
-import { lazy } from 'react';
-
-import AsyncComponentLoader from 'components/AsyncComponentLoader';
+import { asyncComponentLoader } from 'utils';
const routes = [
{
exact: true,
- component: AsyncComponentLoader(lazy(() => import('pages/Welcome'))),
+ component: asyncComponentLoader(_ => import('pages/Welcome')),
path: '/',
},
{
exact: true,
- component: AsyncComponentLoader(lazy(() => import('pages/Page1'))),
+ component: asyncComponentLoader(_ => import('pages/Page1')),
path: '/page-1',
},
{
exact: true,
- component: AsyncComponentLoader(lazy(() => import('pages/Page2'))),
+ component: asyncComponentLoader(_ => import('pages/Page2')),
path: '/page-2',
},
{
exact: true,
- component: AsyncComponentLoader(lazy(() => import('pages/Page3'))),
+ component: asyncComponentLoader(_ => import('pages/Page3')),
path: '/page-3',
},
{
exact: true,
- component: AsyncComponentLoader(lazy(() => import('pages/Page4'))),
+ component: asyncComponentLoader(_ => import('pages/Page4')),
path: '/page-4',
},
{
- component: AsyncComponentLoader(lazy(() => import('components/NotFound'))),
+ component: asyncComponentLoader(_ => import('components/NotFound')),
},
];
diff --git a/src/utils/asyncComponentLoader.js b/src/utils/asyncComponentLoader.js
new file mode 100644
index 0000000..6d82bf0
--- /dev/null
+++ b/src/utils/asyncComponentLoader.js
@@ -0,0 +1,115 @@
+import React, { Suspense, useState, useEffect, lazy } from 'react';
+
+import Loading from 'components/Loading';
+
+import { sleep } from 'utils';
+import { loader } from 'config';
+
+// a little bit complex staff is going on here
+// let me explain it
+
+// usually, we load components asynchronously with `Suspense` and `lazy` by this way
+
+/*
+ }>
+ {lazy(_ => import('path/to/the/component'))}
+
+*/
+
+// here we have two major problems:
+
+// 1) When the loading process is finished "quickly", we will see the fallback component
+// has come-and-gone quickly, which will lead to blinking on the page
+
+// The solution of the first problem is a so-called "delayed fallback", which gives us
+// an opportunity to not show the fallback component if the loading process
+// takes less than a certain amount of time
+// So, the implementation of it is here:
+
+const getDelayedFallback = (LoadingComponent, delay) => props => {
+ const [isDelayPassed, setIsDelayPassed] = useState(false);
+
+ useEffect(_ => {
+ const timerId = setTimeout(_ => setIsDelayPassed(true), delay);
+
+ return _ => clearTimeout(timerId);
+ }, []);
+
+ return isDelayPassed && ;
+}
+
+/* ================================================================================== */
+
+// 2) The second one is the minimum amount of time of fallback render.
+// We said that `DelayedFallback` will not show the fallback component in all cases
+// when the loading process is finished during the `delay` amount of time,
+// but when that process is continuing longer than the `delay`, then the fallback component should
+// be appeared. Okay, now let's consider a situation when the loading process finishes a millisecond
+// after appearing of the fallback component. We will see the fallback component has come-and-gone
+// quickly, which will again lead to blinking on the page.
+
+// The solution of the second problem is to set of a minimum timeout, which will
+// ensure that the falback component will be rendered for that minimum amount of time
+
+const getLazyComponent = (loadComponent, delay, minimumLoading) => lazy(_ => {
+ // fix the moment of starting loading
+ const start = performance.now();
+ // start loading
+ return loadComponent()
+ .then(moduleExports => {
+ // loading is finished
+ const end = performance.now();
+ const diff = end - start;
+
+ // first of all, let's remember that there are two values that user provides us
+ // 1) `loader.delay` - if the loading process is finished during this amount of time
+ // the user will not see the fallback component at all
+ // 2) `loader.minimumLoading` - but if it appears, it will stay rendered for at least
+ // this amount of time
+
+ // so, according to above mentioned, there are three conditions we are interested in
+ // 1) when `diff` is less than `loader.delay`; in this case, we will immediately return
+ // the result, thereby we will prevent the rendering of the fallback
+ // and the main component will be rendered
+ // 2) when `diff` is bigger than `loader.delay` but less than `loader.delay + loader.minimumLoading`;
+ // it means `fallback` component has already been rendering and we have to
+ // wait (starting from this moment) for `loader.delay + loader.minimumLoading - diff`
+ // amount of time
+ // 3) when `diff` is bigger than `loader.delay + loader.minimumLoading`. It means we don't need to wait
+ // anymore and we should immediately return the result as we do it in 1) case.
+
+ // so, in the 1) and 3) cases we return the result immediately, and in 2) case we have to wait
+ // at least for `loader.delay + loader.minimumLoading - diff` amount of time
+
+ if (
+ (diff < loader.delay) || (
+ (diff > loader.delay) && (diff > loader.delay + loader.minimumLoading)
+ )
+ ) {
+ return moduleExports;
+ } else {
+ return sleep(loader.delay + loader.minimumLoading - diff).then(_ => moduleExports);
+ }
+ });
+});
+
+/* ================================================================================== */
+
+// And the combination of these two (plus some "magic" plus some backflips),
+// will secure us from having any kind of blinking in the process of asynchronous loadings
+
+// INFO: the usage of `asyncComponentLoader` looks like this:
+// asyncComponentLoader(_ => import('pages/Welcome'))
+
+const asyncComponentLoader = (loadComponent, loadingProps) => props => {
+ const Fallback = loader.delay ? getDelayedFallback(Loading, loader.delay) : Loading;
+ const LazyComponent = getLazyComponent(loadComponent, loader.delay, loader.minimumLoading);
+
+ return (
+ }>
+
+
+ );
+};
+
+export default asyncComponentLoader;
diff --git a/src/utils/index.js b/src/utils/index.js
index c0e95e9..7bff4ca 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -3,6 +3,8 @@ import noop from './noop';
import isMobile from './isMobile';
import resetApp from './resetApp';
import downloadFile from './downloadFile';
+import sleep from './sleep';
+import asyncComponentLoader from './asyncComponentLoader';
export {
today,
@@ -10,4 +12,6 @@ export {
isMobile,
resetApp,
downloadFile,
+ sleep,
+ asyncComponentLoader,
};
diff --git a/src/utils/sleep.js b/src/utils/sleep.js
new file mode 100644
index 0000000..a56adec
--- /dev/null
+++ b/src/utils/sleep.js
@@ -0,0 +1,3 @@
+const sleep = ms => new Promise(res => setTimeout(res, ms));
+
+export default sleep;
diff --git a/src/utils/today.js b/src/utils/today.js
index 3dd4906..3ff3730 100644
--- a/src/utils/today.js
+++ b/src/utils/today.js
@@ -2,8 +2,6 @@ import dayjs from 'dayjs';
import { dateFormat } from 'config';
-const today = _ => {
- return dayjs().format(dateFormat);
-};
+const today = _ => dayjs().format(dateFormat);
export default today;