I have a react app using MUI and right now I've implemented suspense with a spinner which kicks in as the fallback component while the content is being loaded. I'd love to add a fade in/out transition to the fallback component since the change is way too abrupt at the moment.
My setup (for the relevant parts of this particular question) is as follows:
"@glidejs/glide": "^3.4.1",
"@material-ui/core": "4.8.3",
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "4.0.0-alpha.39",
"@material-ui/pickers": "3.2.10",
"@types/autosuggest-highlight": "^3.1.0",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.7",
"autosuggest-highlight": "^3.1.1",
"connected-react-router": "^6.8.0",
"history": "^4.10.1",
"node-sass": "^4.13.0",
"react": "^16.12.0",
"react-date-picker": "^8.0.0",
"react-dom": "^16.12.0",
"react-feather": "^2.0.3",
"react-redux": "^7.2.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
"redux-devtools-extension": "^2.13.8",
"redux-ducks-ts": "^1.0.9",
"redux-logger": "^3.0.6",
"redux-saga": "^1.1.3",
"reselect": "^4.0.0",
"styled-components": "^4.4.1",
"typescript": "~3.7.2"
This is the main router of the app, it has a RouteWithSubRoutes
component which receives a route as a param and renders a react-render-dom
Route component, but basically acts as a router switching containers
import React, { FC, Suspense } from "react";
import { Switch } from "react-router-dom";
import routes from "./routes";
import { Route, Redirect } from "react-router-dom";
import { SessionContainerProps } from "./types";
// Coponents
import RouteWithSubRoutes from "components/_shared/RouteWithSubRoutes";
import Footer from "components/_shared/Footer";
import SuspenseLoader from "components/_shared/SuspenseLoader";
const SessionContainer: FC<SessionContainerProps> = () => (
<>
<Suspense fallback={<SuspenseLoader />}>
<Switch>
{routes.map((route, i) => (
<RouteWithSubRoutes key={`${i}_${route.path}`} {...route} />
))}
<Route path="/login/*">
<Redirect to="/login" />
</Route>
</Switch>
</Suspense>
<Footer />
</>
);
export default SessionContainer;
As it is right now it's a centered MUI circular progress (Spinner) with a white background that overlays the whole app
import React from "react";
import { CircularProgress } from "@material-ui/core";
const SuspenseLoader = () => {
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
zIndex: 10000,
backgroundColor: "#FFF",
display: "flex",
alignItems: "center",
flexDirection: "column",
justifyContent: "center",
marginTop: "auto",
marginBottom: "auto",
}}
>
<CircularProgress
size="6rem"
style={{
color: "#e8eaef",
margin: 0,
padding: 0,
}}
/>
</div>
);
};
export default SuspenseLoader;
Before we go on in the article, note that React Suspense is an experimental feature and is still undergoing changes, especially with the upcoming release of React 18.
Suspense enable you to perform route-based code-splitting without using an external package. You can simply convert the route components of your app to lazy components and wrap all the routes with a Suspense component.
Suspense is not a data fetching library. It's a mechanism for data fetching libraries to communicate to React that the data a component is reading is not ready yet. React can then wait for it to be ready and update the UI. At Facebook, we use Relay and its new Suspense integration.
You just add some @keyframes animations to Fallback component, and delay its display either by setTimeout and a state flag, or by pure CSS ( animation-fill-mode and -delay used here).
I'll attempt to give this answer a shot. I think you'll have to use a few things.
You'll likely want to use CSS transitions:
.suspenseComponent {
opacity: 1;
transition: opacity 1s;
}
.fade {
opacity: 0;
}
So that if you later change that opacity on the element it will happen over a duration of 1 second.
Then you'll also need useRef (I think), so that you can keep track of that component and manually update the opacity.
import {useRef} from 'react'
const element = useRef(null)
...
<SuspenseComponent ref={element}/>
...
element.current.classList.add('fade')
// essentially the same thing as document.getElementById('suspense').classList.add('fade')
Using the above you can get a reference to an element and you can add or remove classes to it, so you can add the fade class to it.
Lastly you can use:
import {useEffect} from 'react'
const Example = () => {
useEffect(() => {
// anything here will execute on component mount
return () => {
// anything you return here will execute on component unmount
}
}, [])
}
I've not built it myself but I think you should be able to apply the opacity class on component mount so that it transitions in over 1s and then on the useEffect unmount if you remove the class it should transition out over 1s.
If it immediately reloads the component then perhaps you can also include:
setTimeout(() => {
// delay whatever is refreshing the page
}, 1000)
If that might help.
Hopefully those ideas in combination help you get somewhere.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With