So I'd like to implement a loading bar just like github has. It should start loading on a click to another page and finish when it arrived.
I'm using material-ui and for the loader react-progress-bar-plus.
I tried to use react-router's lifecycle hooks, namely componentDidUpdate and componentWillReceiveProps to set the state to be finished.
For start, I attached an onTouchTap function to the menu items but it just does not want to work properly.
What is the best way to implement this feature?
You can use router-resolver with react-progress-bar-plus.
See this example:
http://minhtranite.github.io/router-resolver/ex-4
The usage example:
// app.js
//...
import {RouterResolver} from 'router-resolver';
//...
const routes = {
path: '/',
component: App,
indexRoute: {
component: require('components/pages/PageHome')
},
childRoutes: [
require('./routes/Example1Route'),
require('./routes/Example2Route'),
require('./routes/Example3Route')
]
};
const renderInitial = () => {
return <div>Loading...</div>;
};
const onError = (error) => {
console.log('Error: ', error);
};
ReactDOM.render(
<Router routes={routes}
history={history}
render={props => (
<RouterResolver {...props} renderInitial={renderInitial} onError={onError}/>
)}/>,
document.getElementById('app')
);
And:
// components/pages/PageExample1.js
import React from 'react';
import Document from 'components/common/Document';
class PageExample1 extends React.Component {
static resolve() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('simple data');
}, 2000);
});
};
static propTypes = {
response: React.PropTypes.string.isRequired
};
render() {
return (
<Document title='Example1 | Router resolver' className='page-ex-1'>
<h1>Example 1: {this.props.response}</h1>
</Document>
);
}
}
export default PageExample1;
I made a small package react-router-loading that allows you to show loading indicator and fetch some data before switching the screen.
Just use Switch and Route from this package instead of react-router-dom:
import { Switch, Route } from "react-router-loading";
Add loading props to the Route where you want to wait something:
<Route path="/my-component" component={MyComponent} loading/>
And then somewhere at the end of fetch logic in MyComponent add loadingContext.done();:
import { LoadingContext } from "react-router-loading";
const loadingContext = useContext(LoadingContext);
const loading = async () => {
//fetching some data
//call method to indicate that fetching is done and we are ready to switch
loadingContext.done();
};
Related
I'm trying to get Reach Router to navigate programmatically from one of my components. The URL is updated as expected however the route is not rendered and if I look at the React developer tools I can see the original component is listed as being displayed.
If I refresh the page once at the new URL then it renders correctly.
How can I get it to render the new route?
A simplified example is shown below and I'm using #reach/router#1.2.1 (it may also be salient that I'm using Redux).
import React from 'react';
import { navigate } from '#reach/router';
const ExampleComponent = props => {
navigate('/a/different/url');
return <div />;
};
export default ExampleComponent;
I was running into the same issue with a <NotFound defualt /> route component.
This would change the URL, but React itself didn't change:
import React from "react";
import { RouteComponentProps, navigate } from "#reach/router";
interface INotFoundProps extends RouteComponentProps {}
export const NotFound: React.FC<INotFoundProps> = props => {
// For that it's worth, neither of these worked
// as I would have expected
if (props.navigate !== undefined) {
props.navigate("/");
}
// ...or...
navigate("/", { replace: true });
return null;
};
This changes the URL and renders the new route as I would expect:
...
export const NotFound: React.FC<INotFoundProps> = props => {
React.useEffect(() => {
navigate("/", { replace: true });
}, []);
return null;
};
Could it be that you use #reach/router in combination with redux-first-history? Because I had the same issue and could solve it with the following configuration of my historyContext:
import { globalHistory } from "#reach/router";
// other imports
const historyContext = createReduxHistoryContext({
// your options...
reachGlobalHistory: globalHistory // <-- this option is the important one that fixed my issue
}
More on this in the README of redux-first-history
The same issue happens to me when I'm just starting to play around with Reach Router. Luckily, found the solution not long after.
Inside Reach Router documentation for navigate, it is stated that:
Navigate returns a promise so you can await it. It resolves after React is completely finished rendering the next screen, even with React Suspense.
Hence, use await navigate() work it for me.
import React, {useEffect} from 'react';
import {useStoreState} from "easy-peasy";
import {useNavigate} from "#reach/router";
export default function Home() {
const {isAuthenticated} = useStoreState(state => state.auth)
const navigate = useNavigate()
useEffect(()=> {
async function navigateToLogin() {
await navigate('login')
}
if (!isAuthenticated) {
navigateToLogin()
}
},[navigate,isAuthenticated])
return <div>Home page</div>
}
Try and use gatsby navigate. It uses reach-router. It solved my problem
import { navigate } from 'gatsby'
I'm trying to add some analytics tracking for my react app. Basically just using a component to add global event listeners and then handle the event appropriately in that component.
I want to wrap my entire app in this component and for it to pick up componentWillUpdate prop changes so I can react to page changes using prop.location. My problem is I don't know how to setup my wrapper component to do this. I know the concept of HOC can help wrap one component and I've tested that to work but I want this to be a more generic and global component.
Tracker.js
import PropTypes from "prop-types"
import * as React from "react"
import { connect } from "react-redux"
import TrackingManager from './TrackingManager'
import ScriptManager from "./ScriptManager"
import { isLeftClickEvent } from "../utils/Utils"
const trackingManager = new TrackingManager()
const config = {
useTagManager: true,
tagManagerAccount: 'testCccount',
tagManagerProfile: 'testProfile',
tagManagerEnvironment: 'dev'
}
/**
* compares the locations of 2 components, mostly taken from:
* http://github.com/nfl/react-metrics/blob/master/src/react/locationEquals.js
*
* #param a
* #param b
* #returns {boolean}
*/
function locationEquals(a, b) {
if (!a && !b) {
return true;
}
if ((a && !b) || (!a && b)) {
return false;
}
return (
a.pathname === b.pathname && a.search === b.search && a.state === b.state
);
}
/**
* Tracking container which wraps the supplied Application component.
* #param Application
* #param beforeAction
* #param overrides
* #returns {object}
*/
const track = Application =>
class TrackingContainer extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
this._addClickListener()
this._addSubmitListener()
}
componentWillUnmount() {
// prevent side effects by removing listeners upon unmount
this._removeClickListener()
this._removeSubmitListener()
}
componentDidUpdate(prevProps) {
// if and only if the location has changed we need to track a
// new pageview
if (!locationEquals(this.props.location, prevProps.location)) {
this._handlePageView(this.props)
}
}
_addClickListener = () => {
// bind to body to catch clicks in portaled elements (modals, tooltips, dropdowns)
document.body.addEventListener("click", this._handleClick)
}
_removeClickListener = () => {
document.body.removeEventListener("click", this._handleClick)
}
_addSubmitListener = () => {
document.body.addEventListener("submit", this._handleSubmit)
}
_removeSubmitListener = () => {
document.body.removeEventListener("submit", this._handleSubmit)
}
_handleSubmit = event => {
console.log(event.target.name)
}
_handleClick = event => {
// ensure the mouse click is an event we're interested in processing,
// we have discussed limiting to external links which go outside the
// react application and forcing implementers to use redux actions for
// interal links, however the app is not implemented like that in
// places, eg: Used Search List. so we're not enforcing that restriction
if (!isLeftClickEvent(event)) {
return
}
// Track only events when triggered from a element that has
// the `analytics` data attribute.
if (event.target.dataset.analytics !== undefined) {
trackingManager.event('pageName', 'User')
}
}
_handlePageView = route => {
console.log('CHANGE PAGE EVENT')
console.log(route)
}
/**
* Return tracking script.
*/
_renderTrackingScript() {
/**
* If utag is already loaded on the page we don't want to load it again
*/
if (window.utag !== undefined) return
if (config.useTagManager === false) return
/**
* Load utag script.
*/
return (
<ScriptManager
account={config.tagManagerAccount}
profile={config.tagManagerProfile}
environment={config.tagManagerEnvironment}
/>
)
}
render() {
return (
<React.Fragment>
<Application {...this.props} {...this.state} />
{this.props.children}
{this._renderTrackingScript()}
</React.Fragment>
)
}
}
export default track
With my index.js I want to do something similar to this:
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Switch, Route } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './lib/store'
import history from './lib/history'
import Loadable from 'react-loadable'
import PageLoader from './components/PageLoader/PageLoader'
import {
DEFAULT_PATH,
LOGIN_PATH,
LOGOUT_PATH,
USER_PATH,
} from './lib/paths'
const Login = Loadable({ loader: () => import('./scenes/Auth/Login' /* webpackChunkName: 'login' */), loading: PageLoader })
const Logout = Loadable({ loader: () => import('./scenes/Auth/Logout'/* webpackChunkName: 'logout' */), loading: PageLoader })
const User = Loadable({ loader: () => import('./scenes/Auth/User'/* webpackChunkName: 'user' */), loading: PageLoader })
import Track from './lib/tracking/Tracker'
import './assets/stylesheets/bootstrap.scss'
import './bootstrap-ds.css'
import './index.css'
import './assets/stylesheets/scenes.scss'
ReactDOM.render((
// This is an example of what I want to accomplish
<Track>
<Provider store={store}>
<Router history={history}>
<Switch>
<Route path={LOGIN_PATH} component={Login} />
<Route path={LOGOUT_PATH} component={Logout} />
<Route path={USER_PATH} component={User} />
</Switch>
</Router>
</Provider>
</Track>
), document.getElementById('root'))
So, basically where the <Track> component can wrap the entire app and still use the props and check if they update. Is there a way to do this? What do I need to change?
Context API seems to be your use case here. You want a decoupled way to share data between components in the same tree. Your wrapper could implement a Provider, and all components that are interest on the shared value will implement a Consumer. HOC and render Props are useful to share stateful logic, not state itself.
const { Provider, Consumer } = React.createContext()
const Wrapper = ({children}) =>{
return(
<Provider value={mySharedValue}>
{children}
</Provider>
)
}
const NestedChildren = () =>{
return(
<Consumer>
{context => <div>{context}</div>}
</Consumer>
)
}
const App = () =>{
return(
<Wrapper>
<Child> <NestedChild /> </Child>
</Wrapper>
)
}
We accomplished something like this with react-capture-metrics.
You provide your analytics API to a top level provider like so:
import { MetricsProvider } from 'react-capture-metrics'
const analytics = {
track: (name, properties) => window.analytics.track(name, properties),
page: (name, properties, category) => window.analytics.page(...(category ? [category, name, properties] : [name, properties]))
}
function App () {
return (
<MetricsProvider analytics={analytics} properties={{ appVersion: pkg.version }}>
// ...the rest of your app
</MetricsProvider>
)
}
Then render a PageView component wherever you want to call analytics.page().
function Page() {
const { PageView } = useMetrics({
variantId,
// ...properties to capture
}, { ready: variantId !== undefined })
return (
<PageView
name="Home"
category="Customer"
ready={/* some useState value perhaps */ }
>
// ...
</PageView>
)
}
You can use ready to delay calling the event until all the properties you want to pass are loaded. Or you can use pageKey to call the event when the user navigates to the same page but with different params.
I have a React app (16.8.6) written in TypeScript that uses React Router (5.0.1) and MobX (5.9.4). The navigation works fine and data loads when it should, however, when I click the browser's Back button the URL changes but no state is updated and the page doesn't get re-rendered. I've read endless articles about this issue and about the withRouter fix, which I tried but it doesn't make a difference.
A typical use case is navigating to the summary page, selecting various things which cause new data to load and new history states to get pushed and then going back a couple of steps to where you started. Most of the history pushes occur within the summary component, which handles several routes. I have noticed that when going back from the summary page to the home page the re-rendering happens as it should.
My index.tsx
import { Provider } from 'mobx-react'
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import App from './App'
import * as serviceWorker from './serviceWorker'
import * as Utils from './utils/Utils'
const rootStore = Utils.createStores()
ReactDOM.render(
<Provider {...rootStore }>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
)
serviceWorker.unregister()
My app.tsx
import * as React from 'react'
import { inject, observer } from 'mobx-react'
import { Route, Router, Switch } from 'react-router'
import Home from './pages/Home/Home'
import PackageSummary from './pages/PackageSummary/PackageSummary'
import ErrorPage from './pages/ErrorPage/ErrorPage'
import { STORE_ROUTER } from './constants/Constants'
import { RouterStore } from './stores/RouterStore'
#inject(STORE_ROUTER)
#observer
class App extends React.Component {
private routerStore = this.props[STORE_ROUTER] as RouterStore
public render() {
return (
<Router history={this.routerStore.history}>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/summary/:packageId" component={PackageSummary} />
<Route exact path="/summary/:packageId/:menuName" component={PackageSummary} />
<Route exact path="/summary/:packageId/:menuName/:appName" component={PackageSummary} />
<Route component={ErrorPage} />
</Switch>
</Router>
)
}
}
export default App
My router store
import { RouterStore as BaseRouterStore, syncHistoryWithStore } from 'mobx-react-router'
import { createBrowserHistory } from 'history'
export class RouterStore extends BaseRouterStore {
constructor() {
super()
this.history = syncHistoryWithStore(createBrowserHistory(), this)
}
}
How I create the MobX stores
export const createStores = () => {
const routerStore = new RouterStore()
const packageListStore = new PackageListStore()
const packageSummaryStore = new PackageSummaryStore()
const packageUploadStore = new PackageUploadStore()
return {
[STORE_ROUTER]: routerStore,
[STORE_SUPPORT_PACKAGE_LIST]: packageListStore,
[STORE_SUPPORT_PACKAGE_SUMMARY]: packageSummaryStore,
[STORE_SUPPORT_PACKAGE_UPLOAD]: packageUploadStore
}
}
So my questions are:
How can I get the page to load the proper data when the user goes back/forward via the browser?
If the solution is being able to get MobX to observe changes to the location, how would I do that?
You could implement something like this in your component:
import { inject, observer } from 'mobx-react';
import { observe } from 'mobx';
#inject('routerStore')
#observer
class PackageSummary extends React.Component {
listener = null;
componentDidMount() {
this.listener = observe(this.props.routerStore, 'location', ({ oldValue, newValue }) => {
if (!oldValue || oldValue.pathname !== newValue.pathname) {
// your logic
}
}, true)
}
componentWillUnmount() {
this.listener();
}
}
Problem with this approach is that if you go back from /summary to other page (e.g. '/'), callback will initiate, so you would also need some kind of check which route is this. Because of these kind of complications I would suggest using mobx-state-router, which I found much better to use with MobX.
React router monitors url changes and renders associated component defined for the route aka url.
You have to manually refresh or call a window function to reload.
If I remember correctly, using a browser back function does not reload the page (I might be wrong).
Why not try to detect the back action by a browser and reload the page when detected instead?
You can try the following code to manually reload the page when the browser back button is clicked.
$(window).bind("pageshow", function() {
// Run reload code here.
});
Also out of curiosity, why do you need so many different stores?
In App.js
useEffect(() => {
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload();
}
};
}, []);
From what I understand Nextjs resolves URLs by mapping them to their respective file in pages folder. So pages/about-us.js will be accessable via href="/about-us".
I want to create multiple languages but don't wanna duplicate the necessary components/JS files. So assuming I have an about-us.js with following content:
<Head title={meta}/>
<Nav/>
<MainContent language={lang}/>
<Footer/>
How can I map /pl/about-us to the /about-us.js in the root of pages without creating another about-us.js in /pages/pl/..?
One of the solution I can think of is to pass the language as a query param
Example
// code for page/about-us.js page
import { withRouter } from 'next/router';
const AboutUs = ({ router }) => {
const { lang } = router.query;
return <div>Welcome to next.js! Language = {lang}</div>;
};
export default withRouter(AboutUs);
so If you got to about-us?lang=pl it will show
Welcome to next.js! Language = pl
Or instead of parsing language inside every page, you can use custom app.js with the code something like this
// custom _app.js
import React from 'react'
import App, { Container } from 'next/app'
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
state = {
language: undefined
};
componentDidMount() {
const { router } = this.props;
this.setState({ language: router.query.lang });
}
render () {
const { Component, pageProps } = this.props
return (
<Container>
<Component {...pageProps} language={this.state.langugage} />
</Container>
)
}
}
so every page will have language passed as a param.
Hope this helps.
UPDATE:
to make a custom routing you need to check disabling file-system routing and write some custom server routing
I'm really new to React and I can't figure out how to render a "loading..." screen when a route is being loaded with getComponent. The getComponent call works fine and displays the component, but there's no indication on the UI that anything is happening between the request and the response. That's what I'm trying to figure out.
import Main from './pages/Main.jsx';
import Test from './pages/Test.jsx';
import Home from './pages/Home.jsx';
var Routes = {
path: "/",
component: Main,
indexRoute: {
component: Home
},
childRoutes: [
{
path: "test",
component: Test
},
{
path: "about",
getComponent: function(path, cb) {
require.ensure([], (require) => {
cb(null, require("./pages/about/About.jsx"));
});
}
}
]
};
export default Routes;
After trying to unsuccessfully force a "loading" component to display using onEnter or within the getComponent function, I thought maybe I should try using Redux to set a loading state to true/false and getting my main view component to display a loading screen:
import React from 'react';
import {connect} from 'react-redux';
import NavBar from '../components/Navigation/NavBar.jsx';
import Footer from '../components/Footer.jsx';
import Loading from './Loading.jsx';
import navItems from '../config/navItems.jsx';
import setLoading from '../actions/Loading.jsx';
var Main = React.createClass({
renderPage: function() {
if (this.props.loading) {
return (
<Loading/>
);
} else {
return this.props.children;
}
},
render: function() {
return (
<div>
<header id="main-header">
<NavBar navigation={navItems}/>
</header>
<section id="main-section">
{this.renderPage()}
</section>
<Footer id="main-footer" />
</div>
);
}
});
function mapStateToProps(state) {
return {
loading: state.loading
}
}
export default connect(mapStateToProps)(Main);
This seems to work if I manually set the loading state using an action, which is what I was looking to do. But (and I feel this is going to be a real noob question) I can't figure out how to access the store/dispatcher from within the router.
I'm not sure if I'm using the wrong search terms or whatever, but I'm completely out of ideas and every react-router/redux tutorial seems to skip over what I feel like has to be a common problem.
Can anyone point me in the right direction (and also let me know if what I'm doing is best practice?)?
EDIT: I'll try and clarify this a bit more. In the first code block, you can see that if I click a <Link to="/about"> element then the getComponent function will fire, which will lazy-load the About.jsx component. The problem I am having is I can't figure out how to show some sort of loading indicator/spinner that would appear immediately after clicking the link and then have it get replaced once the component loads.
MORE EDITING: I've tried creating a wrapper component for loading async routes and it seems to work, however it feels really hacky and I'm sure it isn't the right way to go about doing this. Routes code now looks like this:
import Main from './pages/Main.jsx';
import Test from './pages/Test.jsx';
import Home from './pages/Home.jsx';
import AsyncRoute from './pages/AsyncRoute.jsx';
var Routes = {
path: "/",
component: Main,
indexRoute: {
component: Home
},
childRoutes: [
{
path: "test",
component: Test
},
{
path: "about",
component: AsyncRoute("about")
}
]
};
export default Routes;
The AsyncRoute.jsx page looks like this:
import React from 'react';
function getRoute(route, component) {
switch(route) {
// add each route in here
case "about":
require.ensure([], (require) => {
component.Page = require("./about/About.jsx");
component.setState({loading: false});
});
break;
}
}
var AsyncRoute = function(route) {
return React.createClass({
getInitialState: function() {
return {
loading: true
}
},
componentWillMount: function() {
getRoute(route, this);
},
render: function() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
return (
<this.Page/>
);
}
}
});
};
export default AsyncRoute;
If anyone has a better idea, please let me know.
I think I have this figured out. It may or may not be the correct way to go about things, but it seems to work. Also I don't know why I didn't think of this earlier.
First up, move my createStore code to its own file (store.jsx) so I can import it into the main entry point as well as into my Routes.jsx file:
import {createStore} from 'redux';
import rootReducer from '../reducers/Root.jsx';
var store = createStore(rootReducer);
export default store;
Root.jsx looks like this (it's an ugly mess, but I'm just trying to get something that works on a basic level and then I'll clean it up):
import {combineReducers} from 'redux';
import user from './User.jsx';
import test from './Test.jsx';
var loading = function(state = false, action) {
switch (action.type) {
case "load":
return true;
case "stop":
return false;
default:
return state;
}
};
export default combineReducers({
user,
test,
loading
});
I've made a basic component that shows Loading/Loaded depending on the Redux store's value of "loading":
import React from 'react';
import {connect} from 'react-redux';
var Loading = React.createClass({
render: function() {
if (this.props.loading) {
return (
<h1>Loading</h1>
);
} else {
return (
<h1>Loaded</h1>
);
}
}
});
export default connect(state => state)(Loading);
And now my Routes.jsx file looks like this (note I've imported the Redux store):
import Main from './pages/Main.jsx';
import Test from './pages/Test.jsx';
import Home from './pages/Home.jsx';
import store from './config/store.jsx';
var Routes = {
path: "/",
component: Main,
indexRoute: {
component: Home
},
childRoutes: [
{
path: "test",
component: Test
},
{
path: "about",
getComponent: function(path, cb) {
store.dispatch({type: "load"})
require.ensure([], (require) => {
store.dispatch({type: "stop"});
cb(null, require("./pages/about/About.jsx"));
});
}
}
]
};
export default Routes;
This seems to work. As soon as a <Link/> is clicked to go to the /about route, an action is dispatched to set the "loading" state to true in the main store. That causes the <Loading/> component to update itself (I envision it would eventually render a spinner in the corner of the window or something like that). That weird require.ensure([]) function is run to get webpack to do its fancy code splitting, and once the component is loaded then another action is dispatched to set the loading state to false, and the component is rendered.
I'm still really new to React and while this seems to work, I'm not sure if it's the right way to do it. If anyone has a better way, please chime in!
Following the same approach as #David M I implemented a loading reducer and a function to wrap the dispatches.
Excluding the store creation and manage, they are basically as follows:
loadingReducer:
// ------------------------------------
// Constants
// ------------------------------------
export const LOADING = 'LOADING'
// ------------------------------------
// Actions
// ------------------------------------
const loadQueue = []
export const loading = loading => {
if (loading) {
loadQueue.push(true)
} else {
loadQueue.pop()
}
return {
type: LOADING,
payload: loadQueue.length > 0
}
}
export const actions = {
loading
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[LOADING]: (state, action) => (action.payload)
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = false
export default function reducer (state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}
Notice how loadingQueue keeps the loading message active while there are remaining modules to fetch, for nested routes.
withLoader function:
import { loading } from 'loadingReducer'
const withLoader = (fn, store) => {
return (nextState, cb) => {
store.dispatch(loading(true))
fn(nextState, (err, cmp) => {
store.dispatch(loading(false))
cb(err, cmp)
})
}
}
export default withLoader
Now when defining new routes we can dispatch the loading action implicitly using withLoader:
someRoute:
import withLoader from 'withLoader'
import store from 'store'
const route = {
path: 'mypath',
getComponent: withLoader((nextState, cb) => {
require.ensure([], require => {
cb(null, require('something').default)
}, 'NamedBundle')
}, store)
}
export default route
OK, let's see if I can shed some light on this here:
I can't figure out how to access the store/dispatcher from within the router
There is no need to do that AFAIK. You can specify all routes, listing the components that should answer each route (like you did above), and then connect each of the components to the redux store. For connecting, your mapStateToProps function can be written in a much simpler fashion, like this:
export default connect(state => state)(Main);
Regarding the loading state: I think it is a step in the wrong direction to have a slow-loading component and to display a waiting indicator while it is loading. I would rather have a fast-loading component that loads all of its data asynchronously from the backend, and while the data is not yet available, the component renders a waiting indicator. Once the data is available, it can be displayed. That is basically what you sketched in your second edit.
It would be even better if you could drive this off of your actual data, i.e. no data present -> show the loading screen / data present -> show the real screen. This way, you avoid issues in case your loading flag gets out of sync. (More technically speaking: Avoid redundancy.)
So, instead of making the wrapper generic, I would rather create a standalone component for the loading screen and display that whenever each individual component feels the need for it. (These needs are different, so it seems to be difficult to handle this in a generic way.) Something like this:
var Page = function(route) {
return React.createClass({
getInitialState: function() {
// kick off async loading here
},
render: function() {
if (!this.props.myRequiredData) {
return (
<Loading />
);
} else {
return (
// display this.props.myRequiredData
);
}
}
});
};
dynamic load async routers are using require.ensure, which use jsonp to download scripts from network.
because of slow networking, sometime, UI blocks, the screen is still showing the previews react component.
#Nicole , the really slow is not the data loading inside component, but is the component self, because of jsonp