reactjsrouteslazy-loadingframer-motionpage-transition

(React) Lazy Loading components that use Framer-Motion page transitions


Looking up, down, and sideways for a solution to this problem.

My goal is to assign Lazy Loading to nearly all components in my React website. However, these components utilize framer-motion page transitions when these components enter and exit. The combination of page transitions and lazy loading is causing the component to no longer load properly when clicked on as a Route.

I found inspiration from this post (https://github.com/reactjs/react-transition-group/issues/580) and tried to introduce the "react-lazy-with-preload" package (https://github.com/ianschmitz/react-lazy-with-preload), however I was unsuccessful in getting it to work.

Is anyone aware of a solution to this issue? I surely can't be the only person trying to data split a React website which uses components that have framer-motion page transitions.

App.js Code below:

import React, { Suspense } from 'react';
import { Switch, Route, useLocation } from 'react-router-dom';
import lazy from "react-lazy-with-preload";
import { AnimatePresence } from 'framer-motion';

////////////////////////////////////////////////
//PREPARE PAGES OF WEBSITE AS LAZY LOADED PAGES
import Navbar from './components/layout/Navbar/Navbar';
import Footer from './components/layout/Footer/Footer';
import Home from './components/layout/Homepage/Home';
const Dashboard = lazy(() => import('./components/dashboard/Dashboard'));
const Login = lazy(() => import('./components/authorization/login/LoginComponent'));
const SignUp = lazy(() => import('./components/authorization/signup/SignupComponent'));
const OurTeam  = lazy(() => import( './components/layout/OurTeam/OurTeam'));

function App () {

  let location = useLocation();

  //Trying to preload the components which contain Page-Transitions
  OurTeam.preload();
  Login.preload();
  SignUp.preload();
  Dashboard.preload();


    return (
      
        <div className='page-container'>

          <div className='content-wrap'>

            <Navbar />

            <AnimatePresence exitBeforeEnter> {/* Exit page transition occurs before next page loads. */}

            <ScrollToTop/> {/* Causes the screen to scroll to the top after routing occurs. */}

              <Switch location={location} key={location.pathname}> {/* Switch is used to make sure only 1 route is loaded up at a time. location and key are used for page transition animation.*/}
                
                {/*Homepage will not be data-split and will always be loaded */}
                <Route exact path='/' component={Home} />

                <Suspense fallback='Loading...'>
                  <Route path='/OurTeam' component={OurTeam} />
                </Suspense>
                  
                <Suspense fallback='Loading...'>
                  <Route path='/dashboard' component={Dashboard}/>
                </Suspense>

                <Suspense fallback='Loading...'>
                <Route path='/login' component={Login} />
                </Suspense>

                <Suspense fallback='Loading...'>
                  <Route path='/signup' component={SignUp} />
                </Suspense>

              </Switch>

            </AnimatePresence>

          </div>

          <Footer/>
          
        </div>
        
      
    );
    
  //}

}
  
export default App;


Solution

  • ANSWER TO PROBLEM FOR FUTURE READERS

    After reviewing numerous sources and other tutorials, I found a solution to the problem I had.

    In short, rather than Lazy Loading components at the Routes in the "App.js" (as can be seen in the code inside the question above) I decided to Lazy Load inside each component that the Route tag is calling. This works because these Routes actually go to components which will load very specific pages depending on the users screen width (see example code below for my signup page):

    SignupComponent.js BEFORE LAZY LOADING

    import * as React from 'react';
    import { useState, useEffect } from 'react';
    
    import { connect } from 'react-redux'; //The library that holds react and redux together.
    import { Redirect } from 'react-router-dom'; //Used to redirect logged out users to a different page.
    import '../authorization.css'
    
    //Page Transition imports
    import { PageTransition } from '../../layout/PageTransition';
    import {motion} from 'framer-motion';
    
    
    import SignupFullscreen from './SignupFullscreen';
    import SignupSmallFullscreen from './SignupSmallFullscreen';
    import SignupTablet from './SignupTablet';
    import SignupSmallTablet from './SignupSmallTablet';
    import SignupMobile from './SignupMobile';
    
    
    const SignupComponent = (props) => {
    
    
        const { authToken, auth } = props
    
       
        //Using the useWindowSize function to determine width of screen.
        let [width] = useWindowSize();
    
    
     
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //FULLSCREEN.
        if(width >= 1425){
    
            return(
                <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                    <SignupFullscreen/>
                </motion.div>
            );
        }
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //SMALL FULLSCREEN.
        else if( (width>1100)&&(width<=1425) ){
            return(
                <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                    <SignupSmallFullscreen/>
                </motion.div>
    
            );
    
        }
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //FULL TABLET SCREEN.
        else if( (width>650)&&(width<=1100) ){
            return(
    
                <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                    <SignupTablet/>
                </motion.div>
    
            );
    
        }
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //HALF TABLET SCREEN.
        else if( (width>425)&&(width<=650) ){
            return(
    
                <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                    <SignupSmallTablet/>
                </motion.div>
    
            );
    
        }
    
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //MOBILE SCREEN.
        else if( width<=425 ){
            return(
    
                <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                    <SignupMobile/>
                </motion.div>
    
            );
    
        }
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //DEFAULT IS FULLSCREEN
        else {
            return(
    
                <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                    <SignupFullscreen/>
                </motion.div>   
    
            );
        }
    
    }
    
    const mapStateToProps = (state) => {
    
        return {
            authToken: state.auth.authToken,
            authError: state.auth.authError,
            auth: state.firebase.auth
    
        }
    }
    
    const mapDispatchToProps = (dispatch) => {
        
        return {
    
        }
    
    }
    
    //Used to determine width of the screen.
    function useWindowSize() {
        const [size, setSize] = useState([window.innerWidth]);
        useEffect(() => {
            const handleResize = () => {
                setSize([window.innerWidth]);
    
            };
            window.addEventListener("resize", handleResize);
            return () => {
                window.removeEventListener("resize", handleResize);
            }
    
        }, []);
        return size;
    
    }
    
    
    export default connect(mapStateToProps, mapDispatchToProps)(SignupComponent)
    

    To introduce lazy loading, you simply wrap each of these components inside the <React.Suspense> tag as can be seen in the code below.

    SignupComponent.js AFTER LAZY LOADING ADDED

    import * as React from 'react';
    import { useState, useEffect } from 'react';
    
    import { connect } from 'react-redux'; //The library that holds react and redux together.
    import { Redirect } from 'react-router-dom'; //Used to redirect logged out users to a different page.
    import '../authorization.css'
    
    //Page Transition imports
    import { PageTransition } from '../../layout/PageTransition';
    import {motion} from 'framer-motion';
    
    
    const SignupComponent = (props) => {
    
        const SignupFullscreen = React.lazy(() => import('./SignupFullscreen'));
        const SignupSmallFullscreen = React.lazy(() => import('./SignupSmallFullscreen'));
        const SignupTablet = React.lazy(() => import('./SignupTablet'));
        const SignupSmallTablet = React.lazy(() => import('./SignupSmallTablet'));
        const SignupMobile = React.lazy(() => import('./SignupMobile'));
        const { authToken, auth } = props
       
    
    
        //Using the useWindowSize function to determine width of screen.
        let [width] = useWindowSize();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //FULLSCREEN.
        if(width >= 1425){
    
            return(
                <React.Suspense fallback={<p>Loading list...</p>}>
                    <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                        <SignupFullscreen/>
                    </motion.div>
                </React.Suspense>
            );
        }
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //SMALL FULLSCREEN.
        else if( (width>1100)&&(width<=1425) ){
            return(
                <React.Suspense fallback={<p>Loading list...</p>}>
                    <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                        <SignupSmallFullscreen/>
                    </motion.div>
                </React.Suspense>
            );
    
        }
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //FULL TABLET SCREEN.
        else if( (width>650)&&(width<=1100) ){
            return(
                <React.Suspense fallback={<p>Loading list...</p>}>
                    <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                        <SignupTablet/>
                    </motion.div>
                </React.Suspense>
            );
    
        }
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //HALF TABLET SCREEN.
        else if( (width>425)&&(width<=650) ){
            return(
                <React.Suspense fallback={<p>Loading list...</p>}>
                    <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                        <SignupSmallTablet/>
                    </motion.div>
                </React.Suspense>
            );
    
        }
    
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //MOBILE SCREEN.
        else if( width<=425 ){
            return(
                <React.Suspense fallback={<p>Loading list...</p>}>
                    <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                        <SignupMobile/>
                    </motion.div>
                </React.Suspense>
            );
    
        }
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //DEFAULT IS FULLSCREEN
        else {
            return(
                <React.Suspense fallback={<p>Loading list...</p>}>
                    <motion.div exit='exit' variants={PageTransition} initial='hidden' animate='show'>
                        <SignupFullscreen/>
                    </motion.div>   
                </React.Suspense>
            );
        }
    
    }
    
    const mapStateToProps = (state) => {
    
        return {
            authToken: state.auth.authToken,
            authError: state.auth.authError,
            auth: state.firebase.auth
    
        }
    }
    
    const mapDispatchToProps = (dispatch) => {
        
        return {
    
        }
    
    }
    
    //Used to determine width of the screen.
    function useWindowSize() {
        const [size, setSize] = useState([window.innerWidth]);
        useEffect(() => {
            const handleResize = () => {
                setSize([window.innerWidth]);
    
            };
            window.addEventListener("resize", handleResize);
            return () => {
                window.removeEventListener("resize", handleResize);
            }
    
        }, []);
        return size;
    
    }
    
    
    export default connect(mapStateToProps, mapDispatchToProps)(SignupComponent)