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.
Related
I've started to code my first React app and it's awesome, but I can't figure out how to manage css files per-component(so the actual CSS won't load if it is not necessary).
React with webpack(correct me if I'm wrong please) wraps the project in such a way that at every given moment the app loads only what it needs(in terms of JS).
So if I have my main App component with only two buttons visible: btn-01 and btn-02, and inside of this component I have another two: component-01 and component-02, and they are hidden till the corresponded button is clicked(btn-01 for component-01), these components won't be loaded until the actual button is clicked(am I getting this right?), however this is not the same with css as I can tell, because I see the css of each of these(component-01 and component-02) components loaded right away the App is loaded, even though none of the buttons are clicked.
I'm not a big fan of inline styling, but I did test it with css module, but the result is the same in this aspect. So I'm not even sure if this is possible to implement in an easy way.
Here's a code, so perhaps I'm not implementing it correctly, but please don't mind the none-DRY code etc.
So as you may see, the style of Component-01 and -02 are loaded even though there is no need for them at the moment(none of the button is pressed).
App.js
import React, { Component } from "react";
import "./App.css";
import Component_01 from "./Component-01/Component-01";
import Component_02 from "./Component-02/Component-02";
class App extends Component {
state = {
isComponent_01: false,
isComponent_02: false,
};
toggleComponent01 = () => {
this.setState({
isComponent_01: !this.state.isComponent_01,
});
};
toggleComponent02 = () => {
this.setState({
isComponent_02: !this.state.isComponent_02,
});
};
render() {
let showComponent_01 = null;
if (this.state.isComponent_01) {
showComponent_01 = <Component_01 />;
}
let showComponent_02 = null;
if (this.state.isComponent_02) {
showComponent_02 = <Component_02 />;
}
return (
<div className="App">
<button className="btn-01" onClick={this.toggleComponent01}>
Btn-01
</button>
<button className="btn-02" onClick={this.toggleComponent02}>
Btn-02
</button>
{showComponent_01}
{showComponent_02}
</div>
);
}
}
export default App;
Component-01.js (and Component-02.js, just with -02.js)
import React from "react";
import style from "./Component-01.module.css";
function App() {
return <div className={style["component-01"]}>Component-01</div>;
}
export default App;
Trying next with layout pattern:
https://github.com/zeit/next.js/tree/canary/examples/layout-component
And the problem is that Layout component get remounted on every page change. I need to use layout component as a Container so it'll fetch data from server on every mount. How can I prevent layout to get re-mounted? Or am I missing something there?
This helped me for persistent layouts. The author puts together a function that wraps your page components in your Layout component and then passes that fetch function to your _app.js. This way the _app.js is actually the components that renders the Layout but you get to specify which pages use which layout (in case you have multiple layouts).
So you have the flexibility of having multiple layouts throughout your site but those pages that share the same layout will actually share the same layout component and it will not have to be remounted on navigation.
Here is the link to the full article
Persistent Layout Patterns in Next.js
Here are the important code snippets. A page and then _app.js
// /pages/account-settings/basic-information.js
import SiteLayout from '../../components/SiteLayout'
import AccountSettingsLayout from '../../components/AccountSettingsLayout'
const AccountSettingsBasicInformation = () => (
<div>{/* ... */}</div>
)
AccountSettingsBasicInformation.getLayout = page => (
<SiteLayout>
<AccountSettingsLayout>{page}</AccountSettingsLayout>
</SiteLayout>
)
export default AccountSettingsBasicInformation
// /pages/_app.js
import React from 'react'
import App from 'next/app'
class MyApp extends App {
render() {
const { Component, pageProps, router } = this.props
const getLayout = Component.getLayout || (page => page)
return getLayout(<Component {...pageProps}></Component>)
}
}
export default MyApp
If you put your Layout component inside page component it will be re-remounted on page navigation (page switch).
You can wrap your page component with your Layout component inside _app.js, it should prevent it from re-mounting.
Something like this:
// _app.js
import Layout from '../components/Layout';
class MyApp extends App {
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext);
return {
...appProps,
};
}
render() {
const { Component, pageProps } = this.props;
return (
<Layout>
<Component {...pageProps} />
<Layout />
);
}
}
export default MyApp;
Also, make sure you replace all the to <Link href=""></Link>, notice that only have change the Html tag to link.
I struggled because with this for many days, although I was doing everything else correctly, these <a> tags were the culprit that was causing the _app.js remount on page change
Even though this is the topic Layout being mounted again and again, the root cause of this problem is that you have some data loaded in some child component which is getting fetched again and again.
After some fooling around, I found none of these problem is actually what Next.Js or SWR solves. The question, back to square one, is how to streamline a single copy of data to some child component.
Context
Use context as a example.
Config.js
import { createContext } from 'react'
export default createContext({})
_App.js
import Config from '../Config'
export default function App({ Component, pageProps }) {
return (
<Config.Provider value={{ user: { name: 'John' }}}>
<Component {...pageProps} />
</Config.Provider>
)
}
Avatar.js
import { useContext } from 'react'
import Config from '../Config'
function Avatar() {
const { user } = useContext(Config)
return (
<span>
{user.name}
</span>
)
}
export default Avatar
No matter how you mount and dismount, you won't end up with re-render, as long as the _app doesn't.
Writable
The above example is only dealing with readable. If it's writable, you can try to pass a state into context. setUser will take care the set in consumer.
<Provider value={useState({})} />
const [user, setUser] = useContext(Config)
setUser is "cached" and won't be updated. So we can use this function to reset the user anytime in child consumer.
There're other ways, ex. React Recoil. But more or less you are dealing with a state management system to send a copy (either value or function) to somewhere else without touching other nodes. I'll leave this as an answer, since even we solved Layout issue, this problem won't disappear. And if we solve this problem, we don't need to deal with Layout at all.
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.
I am trying to use react-loadable on third party react components. It is just showing blank page. When I used it to code split my own components, it worked fine.
import React from 'react'
import Loadable from 'react-loadable';
const Loading = () => <div>Loading...</div>
const Modal = Loadable({
loader: () => import('react-responsive-modal'),
/*loader: () => import('./My-Own-Component'),//this works fine.*/
loading: Loading
});
class MyTest extends React.Component{
render(){
return(<Modal />)
}
}
export default MyTest
I am sure the way I am using is wrong. Please advise. Thanks.
Just a hunch. Can you try extracting the component inside react-responsive-modal into a custom component and try to see if that works.
I think React loadable is not able to import 3rd party components directly.
I have a component that i'm loading via react-loadable. The component that is being loading has some logic in componentWillReceiveProps for detecting if it needs to get some more data, etc.
When loaded via react loadable, it calls render 1 time, and componentWillReceiveProps is never called.
import React from 'react'
import Loadable from 'react-loadable'
import Loading from 'components/Loading'
const LoadableThing = Loadable({
loader: () => import('components/Thing'),
loading: Loading,
render(loaded, props) {
let Component = loaded.default;
return <Component {...props}/>
}
});
export default LoadableThing
And this component is included in another component that's making some initial data calls and sending the results to the Thing
any thoughts? It's calling return <Component again, but that seems like it's creating a new component in that case.