React preloading components with Lazy + Suspense - reactjs

I'm currently using React 16 with Suspense and Lazy to code-split my codebase. Although I would like to preload components.
In my example below I got two routes. Is there a way to preload Demo as soon Prime did mount? I've tried to create another dynamic import in the componentDidMount of the Prime page, but React.lazy doesn't seem to get that that's the same file as the dynamic import below.
import React, { lazy, Suspense } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import GlobalStyle from 'styles';
import Loading from 'common/Loading';
const Prime = lazy(() => import(/* webpackChunkName: "Prime" */'modules/Prime'));
const Demo = lazy(() => import(/* webpackChunkName: "Demo" */'modules/Demo'));
const App = () => (
<main>
<GlobalStyle />
<Suspense fallback={<Loading>Loading...</Loading>}>
<Switch>
<Route path="/" component={Prime} exact />
<Route path="/demo" component={Demo} />
</Switch>
</Suspense>
</main>
);
export default withRouter(App);
So I've tried different approaches, for example with and without webpackChunkName and different ways of importing the other component in componentDidMount, as seen below. The first two approaches of importing the file in componentDidMount resulted in a Webpack error shown at the bottom of the image below. Only the third proceeded, but made the file 2.[hash].js in the image, only load after the page was visited, and not on componentDidMount
What am I missing here?
Code of modules/Demo.jsx:
import React from 'react';
import LogoIcon from 'vectors/logo.svg';
import PageLink from 'common/PageLink';
import Anchor from 'common/Anchor';
import CenteredSection from 'common/CenteredSection';
const Demo = () => (
<CenteredSection variant="green">
<LogoIcon />
<PageLink to="/" variant="green">Go to home page</PageLink>
</CenteredSection>
);
export default Demo;

This is incredibly easy to do, I think there is a misunderstanding about what lazy() and Suspense are doing under the hood.
The only expectation that React.lazy() has is that it takes a function that returns a Promise that resolves with a default component.
React.lazy(() => Promise<{default: MyComponent}>)
So if you want to preload, all you have to do is execute the promise yourself ahead of time.
// So change this, which will NOT preload
import React from 'react';
const MyLazyComp = React.lazy(() => import('./path/to/component'));
/*********************************************/
// To this, which WILL preload
import React from 'react';
// kicks off immediately when the current file is imported
const componentPromise = import('./path/to/component');
// by the time this gets rendered, your component is probably already loaded
// Suspense still works exactly the same with this.
const MyLazyComp = React.lazy(() => componentPromise);
The fact that this is a known signature makes it for useful for all sorts of other situations. For instance, I have a bunch of components that rely on the google maps api being dynamically loaded, I was able to create a function that loads the google maps api and then imports the component. I won't detail the internals of this example since it's a tangent, but the point is I made myself a function that does a bunch of async stuff and then returns a Promise with an object of {default: Component}.
import React from 'react';
const MyLazyComp = React.lazy(() => importMapsComponent('./path/to/comp'));

Not sure how much help this will be, but here is a code sandbox that works (Demo gets loaded by componentDidMount). It is a considerably simplified version of your code using create-react-app for the config. Perhaps you can take this as a starting point and morph it gradually closer to your app to see what causes the dynamic import to no longer work as desired.

Related

I'm Receiving Invalid Hook Call error either from inside a functional component

In my project I have the index.tsx calling the App.tsx that uses a UserProvider and AuthProvider. I received an invalid call error from inside the UserProvider because I'm using the useState hook.
This problem occurs also if I create a custom hook, I can't use any hook inside the custom hook.
This is my index.tsx:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//import './commons/global.css';
import reportWebVitals from './reportWebVitals';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'),
);
reportWebVitals();
This is my App.tsx:
import { useEffect, useState } from 'react';
import { BrowserRouter, useNavigate } from 'react-router-dom';
import {
AuthService,
AuthProvider,
useAuth,
useLogin,
UserProvider,
} from 'reactjs-oauth2-pkce-provider';
import './index.css';
import Routes from './routes';
const authService = new AuthService({...});
const App = () => {
const [user, setUser] = useState({});
const [loading, setLoading] = useState(true);
// for every refresh of the page, check if there is a user in localStorage
useEffect(() => {
const user = localStorage.getItem('user');
if (user) {
setUser(JSON.parse(user));
}
setLoading(false);
}, []);
return (
<UserProvider>
<AuthProvider authService={authService}>
<Routes />
</AuthProvider>
</UserProvider>
);
};
export default App;
This is my UserProvider.tsx:
import React, { ReactElement, useState } from 'react';
import { UserContext } from '#app/application/UserContext';
export const UserProvider = ({ children }: { children: ReactElement }) => {
const [user, setUser] = useState({});
console.log(user);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
There is no function that is calling the hook outside a functional component (there is?)!
A important thing to note is that the UserProvider.tsx is part of a separate package for authentication that I'm building and importing from it with yarn link.
I already tested to install directly from github without success.
I already tested declaring the function with React.FC type but the result is the same.
The understanding that I have from it until now is that the react do not know that the App() function is a functional component and the call for UserProvider is inside a functional component.
I'm expecting that I can use hooks inside the provider, to work useState, useEffect, useNavigate.
The error message is telling you that your error is in fact originating from your Hook call in UserProvider.tsx at line 5.
When you don't import components traditionally as react Components such as
<Component/>
and instead import them as a function such as
return component();
or as in your case, importing them as a wrapper:
<Component>Parent</Component>
Then React hooks won't work.
I would recommend using Redux for logins or the useContext Hook to manage logins if you can't figure out the Traditional React Hooks way of managing this. useContext is also a React hook which would cause the same error if implemented in a similar way, but Redux wouldn't cause you to adhere to any of these strict React rules..
There's a lot of existing Login Templates on Github, including ones with Redux. Implementing Logins is the most boring and tedious process I've dealt with, which can usually take extremely long. Traditionally I use App Skeletons with login capabilities already implemented, and then add all the fun code over, but try the suggestions I mentioned.
The error message that you posted contains three possible reasons for the error. Based on the information that you've provided, reason #3 seems like the most likely culprit.
You might have mismatching versions of React and React DOM.
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app.
I don't see anywhere in your code, at least not in what you've posted, that you are violating the rules of hooks.
It's what you've said here that jumps out:
A important thing to note is that the UserProvider.tsx is part of a separate package for authentication that I'm building and importing from it with yarn link.
I suspect that your reactjs-oauth2-pkce-provider package is declaring react in the dependencies rather than the peerDependencies. This would cause you to have two copies of react -- one from the package and another from your main app. If these two versions don't match, you could be dealing with reason #1 as well.
The "Duplicate React" section of the docs contains a few checks that you can do to confirm that you do in fact have two copies of React.
If you see more than one React, you’ll need to figure out why this happens and fix your dependency tree. For example, maybe a library you’re using incorrectly specifies react as a dependency (rather than a peer dependency).
Since this is a package that you created and control, you can fix the problem at its root. the solution is to remove react from the dependencies array in your package's package.json and move it to the peerDependencies array instead. You may also need it in the devDependencies. Here is a good example.

Keep state while navigating between component in react-router

Let's say I want to have a reusable react component in my project. I also want that component to have its state under different locations without losing it during component unmount. What is the correct way to deal with this kind of architecture in React? In other words, when the user navigates between these two routes react unmounts the previous component, therefore it loads remote data on every navigation between /user and /groups routes.
I also know that there is something called Redux. I don't see a clear way how to do it using reduct. Do I need two reducers? one for Users and the other for Groups? If so it's quite inconvenient creating a new reducer and write new logic each time when I need to use ReusableComponent.
Here is a similar skeleton to describe what I am trying to do. Any hint would be helpful.
//Router example
<Router>
<Switch>
<Route exact path=”/users” >
<UserComponent>
<ReusableComponent url=”http://apidomain.com/users” />
</UserComponent>
</Route>
<Route exact path=”/groups” >
<GroupComponent>
<ReusableComponent url=”http://apidomain.com/groups” />
</GroupComponent>
</Route>
</Switch>
</Router>
//ReusableComponent Example
<ReusableComponent>
--->use url, that passed from parent component tree(users or groups) to load data and keep in state
<ReusableComponentContext>
<Head />
<Body />
<Footer />
</ReusableComponentContext>
</ReusableComponent>
EDIT
So to describe my problem better is I need to have the same component with two or more parallel state on the different locations without overriding each other. If it's possible
I would use the "React Context" api. The context wrappes your app so if one component updates/ rerenderes the state which is stored inside of the context stayes untouched. To use Context you need three files:
"UserContext" = Example => rename!
Context Component (UserContext)
import { createContext } from "react"
export const UserContext = createContext(initValue)
Parent Component (Provider)
//filename: UserContext.js
//* import React, { useState } from "react"
//* import UserContext from "./UserContext"
const [state, setState] = useState("initState")
//* return(
<UserContext.Provider value={{state, setState}}> //value="props"
<ChildComponent/>
</UserContext.Provider>
Child Component (Consumer)
//*import React, { use Context } from "react";
//*import {UserContext} from "./UserContext"
const data = useContext(UserContext) //here "UserContext"
src: short explenation of usage
Edit: consuming with a custom hook
To avoid one import-statement you can create a custom Hook like this
import React, { use Context } from "react";
import {UserContext} from "./UserContext";
const useUserContext = (()=>{
const {state, setState} = useContext(UserContext)
//use effect if you want to set the context? with the hook...
return[state, setState]
})
in your remounting component
import useUserContext from "./useUserContext"
//rfce{
const {state, setState} = useUserContext()
//}
you can connect ReusableComponent to a piece of your redux store (see connect for more details).
import { connect } from "react-redux";
const ReusableComponent = (props) => {
// some logic before return
return <div>{props.magicProperty}</div>
}
const mapStateToProps = (state) => ({ magicProperty: state.magicProperty });
return connect(mapStateToProps)(ReusableComponent);
So every time you use ReusableComponent in you app, the magicProperty is shared, You can also connect some actions to the component in order to manage that part of state in the classical redux flow.
I think I found the solution. In my case, I had some misunderstanding on what level put context provider tag in the router component tree. So in React, it's very important to put the context provider wrapper in the right location. It holds a dedicated state only for those child components that are wrapped by that context provider.
In my case, I had ReusableComponentContext inside ReusableComponent and that was the wrong approach Because everywhere I used ReusableComponent it had individual context(Therefore individual state). I moved ReusableComponentContext on the top of a couple of components to solve my problem.

Named Lazy import in React Workaround

I made a workaround for named Lazy imports in React I want someone to check if this is actually working as lazy import or not.
Toast.js
import { ToastContainer } from "react-toastify";
export default ToastContainer;
App.js
const ToastContainer = React.lazy(() => import("../main/shared/toast"));
return(
<Suspense fallback={null}>
<ToastContainer />
</Suspense>
);
As React official documentation said "currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don’t pull in unused components."
https://reactjs.org/docs/code-splitting.html
It is working as a lazy component you export this as a default component in 2nd line
The accepted answer, and the documentation that it refers to, are slightly misleading.
lazy() expects a thenable function, meaning that you can load your module and then select which named export you want to import. Using your example, that would become:
React.lazy(async () => {
return {
default: (import("../main/shared/toast")).ToastContainer
}
})

useState() Breaks Gatsbyjs/React

I have imported useStats into my index page but when I use it it breaks gatsby/react and I get this error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer
(such as React DOM)
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app See fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
I tried to trouble shoot using this from the site:
// Add this in node_modules/react-dom/index.js
window.React1 = require('react');
// Add this in your component file
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);
But I got back true.
Here is my code:
import React, { useState } from "react";
import { Link } from "gatsby";
// components
import Layout from "../components/Layout/Layout";
import SEO from "../components/seo";
import IndexComponent from "../components/IndexComponent/IndexComponent";
const IndexPage = () => {
const [sku] = useState();
return (
<Layout>
<SEO title="Home" />
<IndexComponent />
</Layout>
);
};
export default IndexPage;
1.) you need [sku, setSku] = useState().
2.) Where are you rendering IndexPage? Are you doing IndexPage() instead of <IndexPage />?
I think It is a terminal Issue with windows.
Seams to work fine with bash.

Bad way of loading pages with react-loadable

I'm using React + React-Router + React-loadable for adding a preLoader page before downloading all content of the page.
Here is my code :
AppLoader.js :
import Loadable from 'react-loadable';
import Loading from './preLoader';
import React from 'react';
const LoadableComponent = Loadable({
loader: () => import('./App'),
loading: Loading,
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
preloader.js :
import React from 'react';
import classes from '../../styles/main/preload.less';
let opacityInterval;
export default class PreLoadPage extends React.Component{
render(){
return(
<div className={classes.rootTag}>
Loading...
</div>
);
}
}
I want to show the loading page until the App page is completely ready. and all the images and anything else have been downloaded. maybe it's problem with
the contents that will be given to react by the server. I'm using django-rest-framework for the backend.
How could i tell the loader to wait until the page was completely rendered and then disappear?
Or mayebe :
How could I tell react to complete mounting component after loading all the contents ( including images)?
Right around the time that this question was asked, react released version 16.6.0, which included React.lazy. Additionally, a Suspense component is provided to render a fallback, or loading component/animation.
The following will wait until all content (files/chunks/resources) is retrieved from your server before rendering your lazy loaded component:
const App = React.lazy(() => import('./App'))
const AppWrapper = () => {
return (
<Suspense fallback={<Loading />}>
<App />
</Suspense>
);
};
With that said, it is probably not optimal to lazy load your entire application. The react docs recommend starting by lazy loading each route. This will split your app up into logical chunks. Once you become more comfortable with code-splitting, other places where it makes sense to split your code will become more apparent.

Resources