How to show boot interface when calling getComponent in reaction router?

I'm really new to React, and I can't figure out how to display the "loading ..." screen when a route is loaded using getComponent. The getComponent call works fine and displays the component, but there is no indication in the user interface that anything is happening between the request and the response. This is what I am trying to understand.

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 the β€œdownload” component to be displayed using onEnter or inside the getComponent function, I thought maybe I should try using Redux to set the load state to true / false and get my main view component to display the 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 boot status using the action I wanted to do. But (and I feel that this will be the real question about the noob). I cannot figure out how to access the repository / dispatcher from inside the router.

I'm not sure if I'm using the wrong search terms or anything else, but I have absolutely no ideas, and every response-router / pruning tutorial seems to miss what I feel should be a common problem.

Can someone point me in the right direction (and also let me know if what I'm doing is really the best practice?)?

EDIT . I will try to clarify this a bit more. In the first block of code, you can see that if I click on the <Link to="/about"> element, then the getComponent function will work, which will lazy load the About.jsx component. The problem I am experiencing is that I cannot figure out how to show any loading indicator / counter that will appear immediately after clicking the link, and then replace it after the component is loaded.

MORE EDITING . I tried to create a wrapper component to load asynchronous routes, and it seems to work, however it feels really hacked, and I'm sure this is the wrong way to go about it. The route 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.

+5
source share
4 answers

I think I figured it out. This may or may not be the right way to do things, but it seems to work. Also, I do not know why I did not think about this before.

First move my createStore code to your own file (store.jsx) so that I can import it into the main entry point, as well as into the 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 (this is an ugly mess, but I'm just trying to get something that works at a basic level, and then I 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 created a basic component that shows "Download / Download" depending on the value of the Redux repository "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 that I imported the Redux repository):

 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; 

It seems to work. As soon as <Link/> pressed to go to the / about route, an action is sent to set the boot status to true in the main repository. This causes the <Loading/> component to be updated (I assume that it will eventually display a counter in the corner of the window or something like that). This strange require.ensure([]) function is run to force webpack to split into a fantastic codeword, and after loading the component, another action is then sent to set the load condition to false and the component is displayed.

I'm still new to React, and although this seems to work, I'm not sure if this is the right thing to do. If anyone has a better way, please call!

+1
source

Following the same approach as @David M, I implemented a load reducer and a function for transferring shipments.

Excluding store creation and management, they are mainly 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 } 

Note that loadingQueue keeps the loaded message active while there are other modules to retrieve for nested routes.

with Loader 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 send 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 
+1
source

Ok, let's see if I can shed some light on this here:

I cannot figure out how to access the repository / dispatcher from the router

No need to do it AFAIK. You can specify all routes by specifying the components that should respond to each route (for example, you did above), and then connect each of the components to the redux repository. To connect, your mapStateToProps function can be written much simpler, for example:

 export default connect(state => state)(Main); 

Regarding the loading state: I believe that the step in the wrong direction has a component with slow loading and displaying a standby indicator during loading. I would prefer to have a fast boot component that asynchronously loads all its data from the backend, and while the data is not yet available, the component displays a wait indicator. Once the data is available, it can be displayed. This is basically what you sketched in the second edit.

It would be even better if you could disconnect this from your actual data, i.e. there is no data β†’ show boot screen / data β†’ show real screen. This way you avoid problems if your download flag does not sync. (More technically: avoid redundancy.)

So, instead of creating a common wrapper, I would prefer to create a separate component for the loading screen and display it when every single component feels the need for it. (These needs are different, so it’s difficult to deal with this in a general 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 ); } } }); }; 
0
source

dynamic asynchronous routers use require.ensure , which jsonp uses to download scripts from the network. due to slow networks, once the user interface blocks, the component that responds to the preview is still displayed on the screen.

@Nicole, really slow - this is not loading data inside the component, but the component itself, due to jsonp

0
source

Source: https://habr.com/ru/post/1246208/


All Articles