I'm using lazy to split my routes and I wanna know if there is any way I can get the loading progress in lazy and suspense.
Currently I'm using it like this.
const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));
function App() {
return (
<Router>
<Switch>
<Suspense fallback={<div>loading</div>}>
<Route path="/" exact={true} component={Home} />
<Route path="/About" component={About} />
</Suspense>
</Switch>
</Router>
);
}
■ But I want to show the loading progress (like youtube).
■ Is there any way I can retrieve the progress for example like below.
<Suspense fallback={({progress}) => <LoadingBar progress={progress}/>}>
lazy uses JavaScript promises which either resolve or reject, but they don't report any progress.
You can however fake the progress with something like http://ricostacruz.com/nprogress/ which increments the progress bar randomly.
This is my solution:
const LazyLoad = () => {
useEffect(() => {
NProgress.start();
return () => {
NProgress.stop();
};
});
return '';
};
<Suspense fallback={<LazyLoad />}>
The javascript way
Install nprogress
npm i nprogress
Import CSS globaly
you may customize the nprogress layout in your global css to make it suitable for your website
import "nprogress/nprogress.css"
React lazy uses promise to suspense its component so we can use it to control our nprogress package
Lazy -----> Promise ----> resolve ----> render
here is how to do it
const MyPage = React.lazy(() => mew Promise(
(resolve, reject) => {
NProgress.start()
import("path/to/page").then(() => {
NProgress.done()
resolve(module)
}).catch(err) => {
NProgress.done()
reject(err)
})
})
)
then you can wrap it into a suspense component and add a loading.
<Route path="/" exact={true} component={
{/* i use a custom loading component (dot animations) */}
<Suspense fallback={() => "..."}>
<MyPage />
</Suspense>
</Route>
this would be a close example of what you want, however you can't make the loading as real as the promise itself. usually others use this solution aswell, so don't overthink it and leave it as is
Related
Am using react router v6 and i would like to use the new loader to load the data before the component loads. So i have the following
In my index.js
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="*"
loader={async ({ params }) => {
console.log("index loader log"); //this logs
return true;
}}
element={<App />}
> </Route>
)
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<RouterProvider router={router} />
);
in my app component i have nested routes like
const App = () => {
return (
<>
<Routes>
<Route path="auth/*" element={<AuthLayout/>}/>
<Route path="about"
loader={async ({ params }) => {
console.log("about child loader log"); //this doesnt log
return true;
}}
element={<AboutPage/>}/>
</Routes>
<h1>Testing app</h1>
</>
);
}
On the app component the loader on the Route path="about" does not console.log when i visit the about route but the component is rendered. What am i missing for the loader to work on the child route.
Based on some basic testing it seems that in order for the new RRDv6.4 data APIs to work you need to specify the complete routing configuration in the createBrowserRouter function.
There does however appear to already be an issue filed with #remix-run/react-router for this behavior as a reported bug, so you may want to follow it if it ever addressed/resolved. (I suspect it was you since the name is "geoffrey" and the timing is coincidentally about an hour ago around the same time as this post)
This above issue has since been closed with comment:
Descendant <Routes> trees do not participate in data loading
(https://reactrouter.com/en/main/components/routes) since they cannot
be known ahead of render-time. You'll need to lift your descendant
route definitions up into the routes you pass to createBrowserRouter.
The relevant information regarding the descendent routes and the new Data API can be found in the Routes documentation in a note.
Note:
If you're using a data router like createBrowserRouter it is
uncommon to use this component as it does not participate in data
loading.
Hoist the entire route declaration to the parent creating the data router. The following does work with the loader function for the "/about" route and About component.
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="*"
loader={({ params }) => {
console.log("index loader log");
return "This is the App";
}}
element={<App />}
>
<Route path="auth/*" element={<AuthLayout />} />
<Route
path="about"
loader={({ params }) => {
console.log("about child loader log");
return "this is the about page";
}}
element={<AboutPage />}
/>
</Route>
)
);
The App component should render an Outlet for the nested routes to render their content into.
import { Outlet } from 'react-router-dom';
const App = () => {
return (
<>
<h1>Testing app</h1>
<Outlet />
</>
);
};
Currently have a problem where when my users use their browsers built in forward/back buttons it doesn't reload the state of my page. (Due to the states being set and rendered in a useEffect)
To fix this I have tried to monitor when the location changes (eg, they go to another route) when this happens I force reload the page.
However, this results in an infinite loop of refreshing. I'm not sure how to counter this?
I am on react router v6.3
export const App = () => {
let location = useLocation();
useEffect(() => {
window.location.reload();
}, [location]);
return (
<Routes>
<Route exact path="/" element={<HomePage />} />
<Route exact path="/example" element={<Callback />} />
</Routes>
);
};
export const Router = () => {
return (
<BrowserRouter>
<App />
</BrowserRouter>
);
};
I'm trying to build an app with Ionic React. I have an issue with the router.
App.tsx
<IonRouterOutlet>
<Route path="/stats" render={() => <Stats/>} exact={true} />
<Route path="/orders" component={Orders} exact={true} />
<Route path="/" render={() => <Redirect to="/stats" />} exact={true} /></IonRouterOutlet>
In my component Stats, I use useEffect to load the data from the API
useEffect(() => {
OrdersService.getOrders().then(resultat => {
setDataOrders(resultat);
});
return function cleanup() {
// Clean the data
}
}, []);
If I go to my component Orders and go back to stats, react don't reload the data from the API.
Do you have any solution to force the reload ?
Thanks in advance.
Ionic works differently to how you might expect, the routes are rendered even if they have not been entered yet. And when you navigate to a new route, the cleanup function of useEffect does not get called. This is by design so routes are already rendered and good to go when you navigate between pages.
What you want to use is the useIonViewWillEnter hook which will be called when the route is about to be entered.
import React, { useState } from "react";
import { useIonViewWillEnter } from "#ionic/react";
export const DataOrdersView = () => {
const [dataOrders, setDataOrders] = useState([]);
useIonViewWillEnter(() => {
OrdersService.getOrders().then((resultat) => {
setDataOrders(resultat);
});
});
return <div>Your View</div>;
};
You can read more here,
https://ionicframework.com/docs/react/lifecycle
I've a condition where I've 3 components
A.js B.js C.js
In old code
routes.js
const A = lazy(() => import("../test/A"));
const B = lazy(() => import("../test/B"));
const C = lazy(() => import("../test/C"));
<Route
path="/test"
render={({ match: { url } }) => (
<Switch>
<Route exact path={`${url}/a-test`} component={A} />
<Route exact path={`${url}/b-test`} component={B} />
<Route exact path={`${url}/c-test`} component={C} />
</Switch>
)}
/>
What I want to achieve
By the above code we can conclude that every time the separate builds
for A, B, C will get download due to lazy load, but I want to achieve
a functionality where I can download all 3 of them altogether, i.e.
When a download a single component using that only I can download all
three of them as a single separate build
What I tried - New Code
combine_module.js
import A from "./A";
import B from "./B";
import C from "./C";
const test = {A, B, C};
export default test;
routes.js
const Test = lazy(() => import("../test/combine_module"));
<Route
path="/test"
render={({ match: { url } }) => (
<Switch>
<Route exact path={`${url}/a-test`} component={Test.A} />
<Route exact path={`${url}/b-test`} component={Test.B} />
<Route exact path={`${url}/c-test`} component={Test.C} />
</Switch>
)}
/>
Code
Full code link
My Issue
I don't know about the approach whether it's wrong or right as I'm not getting any error or any warning related to this in the console but seeing a blank screen cannot see the output, So can please anybody tell how to make this thing right and achieve the desired functionality.
According to the React.lazy documentation:
React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.
So the result of React.lazy is not a module but a special object Suspense can work with.
A usual module can be loaded lazily with WebPack's dynamic import. There is a tricky part with this because the module can be loaded after the component was rendered. So two things we need to do:
1) ensure the component does not render undefined while module is being loaded
2) update the component when the module has been loaded
Updating is quite simple we can make a hook that will set the module as state on load
function useDynamicModule(path) {
const [module, setModule] = useState({});
const [error, setError] = useState();
useEffect(() => {
import(path).then(setModule, setError);
}, []);
return error ? { $error: error } : module.default
}
Usage:
export default function App() {
const Test = useDynamicModule("./CombineModule");
if (!Test) return null;
if (Test.$error) return "Error";
return (
<Router history={history}>
// ...
</Router>
);
}
Playground
I'm trying to apply code splitting to my React app. The below code is my routes with code splitting applied by Loadable from react-loadable. With the implementation, every time I access each page, the code chunk from webpack is properly loaded. However, when I access my webpage initially, it brings about screen flicker initially. After that, if I move to another page, no more screen flicker. It only happens when accessing my website initially.
Does anyone know why it's happening? Is there something wrong on my configuration?
By the way, I used server-side rendering with Node.js in my React app. Does it have something to do with screen flicker by code splitting?
routes.jsx
import React from "react";
import Loadable from "react-loadable";
import { Route, Switch } from "react-router-dom";
import NotFound from "routes/not-found";
const Landing = Loadable({
loader: () => import(/* webpackChunkName: "landing" */ "routes/landing"),
loading: () => null,
modules: ["landing"]
});
const About = Loadable({
loader: () => import(/* webpackChunkName: "about" */ "routes/about"),
loading: () => null,
modules: ["about"]
});
export default props => {
return (
<Switch>
<Route
exact
path="/"
render={routeProps => <Landing {...routeProps} options={options} />}
/>
{/* <Route exact path="/" component={Homepage} /> */}
<Route
exact
path="/about"
render={routeProps => <About {...routeProps} options={options} />}
/>
<Route component={NotFound} />
</Switch>
);
};
src/index.js
const { store, history } = createStore();
const generateClassName = createGenerateClassName();
const Application = (
<JssProvider generateClassName={generateClassName}>
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Frontload noServerRender={true}>
<App />
</Frontload>
</ConnectedRouter>
</Provider>
</MuiThemeProvider>
</JssProvider>
);
const root = document.querySelector("#root");
if (root.hasChildNodes() === true) {
// If it's an SSR, we use hydrate to get fast page loads by just
// attaching event listeners after the initial render
Loadable.preloadReady().then(() => {
hydrate(Application, root, () => {
document.getElementById("jss-styles").remove();
});
});
} else {
// If we're not running on the server, just render like normal
render(Application, root);
}
the flickering is happening because the router don't have the source of your component when routing and it renders null as you defined in loading func provided to Loadable HOC
to prevent flickering you can render a Loading component that should look something like this: https://github.com/osamu38/react-loadable-router-example/blob/master/src/components/Loading.js
or you can preload all the pages onComponentDidMount like this (you should convert your component to class component for it):
componentDidMount = () => {
About.preload()
Landing.preload()
}