Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to apply a fade in/out transition with React suspense for the fallback component?

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;

like image 979
guido732 Avatar asked May 02 '20 02:05

guido732


People also ask

Is React suspense still experimental?

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.

What is suspense and lazy in React?

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.

How suspense works in React?

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.

How do you delay a React lazy?

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).


1 Answers

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.

like image 78
Matriarx Avatar answered Sep 24 '22 16:09

Matriarx