16

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:

Dependencies

    "@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"

Router block

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;

SuspenseLoader component detail

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;

guido732
  • 441
  • 1
  • 4
  • 10
  • in css, you can add `@keyframes` and change the `opacity` at different % – upog May 02 '20 at 02:22
  • 2
    thanks for the reply @upog, do you know of a way the keyframing animation could be triggered on component unmount? Maybe I'm missing something obvious and the whole solution is simpler than what I thought, but the fallback components don't seem to have normal lifecycle methods as any other component would and I can't trigger css animations since I don't seem to have control (of visibility) of the mount/unmount events. – guido732 May 02 '20 at 20:35
  • Have you found a solution to this by now? – Spray'n'Pray Oct 16 '20 at 17:12
  • Nope, sorry. Still haven't found anything useful, I've been dealing with abrupt transitions between fallback/main components. – guido732 Oct 17 '20 at 19:37
  • 2
    @Spray'n'Pray guido732 [here](https://stackoverflow.com/questions/54158994/react-suspense-lazy-delay/61598220#61598220) is an example of delayed fade-in and -out animation with React suspense, I had done some time ago (not sure if it fits your case). – ford04 Oct 17 '20 at 20:24

1 Answers1

2

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.