What is the use of require.ensure in react router - reactjs

I know how simple Router works like this:
<Router history={hashHistory}>
<Route path='/' component={Home} />
<Route path='/address' component={Address} />
</Router>
But I came along this type of routing, which I am not able to understand. Can somebody please explain how the following is working? Or please edit my question as more suitable to these type of routes.
I can only understand path in every Route that if I type this on browser then this Route will be triggered. But I am not able to understand what its running, what components and other things. Please tell me what this type of routing is called and more info on this.
File: routes.js
import React from 'react';
import { Router, Route } from 'react-router';
import App from './app';
export default (
<Router>
<Route name="root" component={App}>
<Route name="home" path="/" getComponent={(location, cb) => {
require.ensure([], (require) => {
cb(null, require('./components/routes/home').default);
});
}}
/>
<Route name="homeSeries" path="/series/:series_id" getComponent={(location, cb) => {
require.ensure([], (require) => {
cb(null, require('./components/routes/home').default);
});
}}
/>
<Route name="homeStatus" path="/status/:status" getComponent={(location, cb) => {
require.ensure([], (require) => {
cb(null, require('./components/routes/home').default);
});
}}
/>
...
...
Lots of more Route
...
...
</Route>
</Router>
);

This routing also works same as the routing you described above. So for example
<Route name="home" path="/" getComponent={(location, cb) => {
require.ensure([], (require) => {
cb(null, require('./components/routes/home').default);
});
}}
/>
The above code will load './components/routes/home' component on "/" route.
Actually this kind of routing is usually used for code splitting (e.g. in webpack module bundler).
Usually webpack bundle the all files into a single bundle file. So in your first code
<Router history={hashHistory}>
<Route path='/' component={Home} />
<Route path='/address' component={Address} />
webpack will bundle 'home' and 'address' components into a single module.
But if you use require.ensure like the second code snippet, webpack will divide the module into multiple modules (called chunks) at each require.ensure.
This is useful because user will only download the required module at initial time. So for example if user goes to /address, then only address chunk will be downloaded at first and not home component (which can be downloaded asynchronously later).

That example is using require.ensure to asynchronously load components. This is most likely being done to allow for code splitting (eg. with Webpack). Code splitting allows you to split your bundle across multiple files, decreasing the amount of code that has to be sent on initial load and only requesting extra code when it is necessary. You can read more about code splitting in the Webpack documentation.
The <Route>s are using the getComponent prop, which is React Router's way for allowing asynchronously loaded components. getComponent expects a function which takes a location and a callback function. The asynchronously loaded component is passed to the <Route> using the callback function. Once the callback has been called, the loaded component will be rendered.

Related

In React with Redux, what does this snippet mean?

Joined a new team, and found below snippet:
const bookListContainer = (location, cb) => {
require.ensure([], require => {
cb(null, require('somepath').default)
}, 'bookList')
}
<Route path="/" component={App}>
<Route path="home" component={HomeComponent} />
<Route path="bookList/:bookId" getComponent={bookListContainer} />
</Route>
what is the difference between component and getComponent? And for bookListContainer, what does it exact do? cannot understand require.ensure()
thanks
I recommend you to read code splitting: https://webpack.github.io/docs/code-splitting.html
You will learn some important concepts there to understand this code.
Regards!
This is a way to asynchronously load routes by splitting them into separate bundles.
getComponent will fire when the route is matched and consequently require what is necessary for that bundle bookList
This is a way to assist reducing the initial load time of your application.

Implement nested async route fetching in React

Is it possible to implement nested async route fetching with System.import so an application is only divided into several reasonable chunks, rather than many small chunks?
Background
I'm in the process of implementing bundle splitting in my React app.
Initially I implemented bundle-loader (which I couldn't get to work in all cases) and then using System.import, which I found to behave much more predictably.
Problem
The code splitting is working just fine on a route by route basis, however, it produces many small bundles and the overhead of an additional bundle and get is unnecessary and wasteful.
As an example, I have this code which loads the bundle for Dashboard, Settings or Images when you navigate to their page:
<Provider store={store}>
<Router history={browserHistory}>
<Route path='/' component={Container}>
<IndexRedirect to="Login" />
<Route path='Registration' component={RegistrationContainer} />
<Route path='Login' component={LoginContainer} />
<Route path='Route1' >
<IndexRedirect to="Dashboard" />
<Route path='Settings'
getComponent={ (loc, cb)=> {System.import('./components/Route1/Settings')
.then(loadRoute(cb))
.catch(errorLoading); }}/>
<Route path='Dashboard'
getComponent={ (loc, cb)=> {System.import('./components/Route1/Dashboard')
.then(loadRoute(cb))
.catch(errorLoading); }}
/>
<Route path='Images'
getComponent={ (loc, cb)=> {System.import('./components/Route1/Images')
.then(loadRoute(cb))
.catch(errorLoading); }}
/>
</Router>
</Provider>
Is it possible to load the bundles for all three when first navigating to the Route1 path?
Many Thanks
I have played around and managed to find a solution which reduces the number of get requests to 2 for any given number of sub routes.
The way I got this result was to introduce an index component to Route1
<Route path='Patient' getComponent={ (loc, cb)=> {System.import('./components/Route1Index')
.then(loadRoute(cb))
.catch(errorLoading); }}>
I then created the index component with imports for all the child routes like this:
import React from 'react';
// Child route components
import Settings from './Settings';
import Dashboard from './Dashboard';
import Images from './Images';
export default (props) =>(
<div>
{props.children}
</div>
)
I also set minChunks:2 in the Webpack CommonsChunkPlugin
Now when I navigate to any child route of Route1 there are only 2 bundles loaded: the Route1Index bundle and the bundle containing all of it's imports

React Router Confirm Navigation to a page

I am working to learn React. The sample project was designed to be used with React 0.13.3 and react-router 0.13.3. I am using react 15.4.1 and react-router 3.0.0
The sample project uses willTransitionTo to do a simple confirmation before allowing the user to navigate to a page. here is the code:
var About = React.createClass({
statics: {
willTransitionTo: function(transition, params, query, callback) {
if (!confirm('Are you sure you want to read a page that\'s this boring?')) {
transition.about();
} else {
callback();
}
}, ...
I know in the version of react-router I am using, the above no longer works. So following the auth-flow example found on the react router docs page I converted my code to use the onEnter convention. here is what I have thus far:
function confirmTransition(nextState, replace){
if(comfirm('Are you sure you want to read a page that\'s this boring?')){
replace({
pathname: '/about',
state: {nextPathname: nextState.location.pathname}
});
}
}
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={require('./components/app')}>
<IndexRoute component={require('./components/homePage')} />
<Route path="/authors" component={require('./components/authors/authorPage')} />
<Route path="/about" component={require('./components/about/aboutPage')} />
<Redirect from="about-us" to="about" />
<Route path='*' component={require('./components/notFoundPage')} onEnter={confirmTransition}/>
</Route>
</Router>
), document.getElementById('app'));
The issue I'm having is when I try to navigate to the about page, the onEnter does not fire.
I'm certain I am missing something. What might I be doing wrong?
Ok. I'm... well not intelligent. I found I had placed the onEnter on the wrong route.
Thank you all for not pointing that out.

React-Router: How to replace this calls in the Route callbacks

When setting a react-router app, I often set it on callbacks to represent authorization filters. this leads me to such designs:
# before, in my root component
requireAuth() { ... }
noAuth() { ... }
render() {
return (
<Router history={this.props.history}>
<Route path='/' component={App} onEnter={this.requireAuth}>
<Route path='toys' component={Toys}/>
...
<Route path='/auth' component={Auth} onEnter={this.noAuth}>
...
</Router>
)
}
I am now trying to port this to nodejs and render the page in the server. Suddenly, I'm asked to group all the routes in a const. Second, I lose my root component and those this. callback binds, and am not able to recreate the same architecture on the server.
So my first problem is the route. In react, I can't return groups of components, but a component which encapsulates all the rest. So, this was my attempt:
# root component render
# after import routes from './routes'
render() {
return (
<Router routes={routes} history={this.props.history}/>
);
}
# in routes.js
module.exports = (
<Route> # is this right?
<Route path='/' component={App} onEnter={this.requireAuth}>
<Route path='toys' component={Toys}/>
...
<Route path='/auth' component={Auth} onEnter={this.noAuth}>
...
</Route>
So, this doesn't seem to cut it and I get a few errors. First, I'm encapsulating all routes inside a main Route component. Is this correct, from a react-router perspective? Can I have kind of empty-encapsulating components?
Second would be: How do I bind those this. callbacks on the server side now? Since I'm following the tutorials, My express router looks smth like this:
import routes from './src/components/routes';
...
app.get('*', (req, res) => {
match(
{ routes, location: req.url },
(err, redirectLocation, renderProps) => {
....
But it's breaking with:
onEnter: undefined.requireAuth,
^
TypeError: Cannot read property 'requireAuth' of undefined
Which is right, as routes constant is not bound to any context.
Or is there a more proper way to do this in react-router, and binding callbacks to routes is the wrong approach?

Using two independent paths in React Router

I am currently using React Router and have routes that use the browserHistory, for example:
import { Router, Route, IndexRedirect, browserHistory } from 'react-router';
<Router history={browserHistory}>
<Route path="tasks" component={Tasks}>
<IndexRoute component={List} />
<Route path="/:id" component={Item} />
</Route>
</Router>
Which allows me to do routes such as:
/tasks/
/tasks/1234
This works, but we have come across a situation where we have two views that are displayed at the same time. We'd like for the link to be shareable and have the app open with both views.
So for example, if we have tasks on the left side of the screen, and a shop on the right, we'd like for there to be two independent parts of the path, something like:
/tasks/1234/#/shop/item/xyz
The shop route should be independent of the left of the hash, and the tasks route should be independent of the right of the hash, so that /new-feature/xyz/#/shop/item/xyz should still render the same view on the right side of the window.
Is it possible to have React Router do routes like this? Or will I have to write a custom router to solve this?
I'm guessing I'd basically have to combine the browserHistory and hashHistory together, but I don't think that's possible with React Router out of the box.
I think rolling your own router just to handle this case might be a little overboard. You can have as many different paths in your route config as you want, and access param information in your components:
<Router history={browserHistory}>
<Route path="/" component={App} />
<Route path="/tasks/:taskId" component={App} />
<Route path="/shop/:shopId" component={App} />
<Route path="/tasks/:taskId/shop/:shopId" component={App} />
</Router>
let App = ({ params }) => // simplified
<div>
{ params.shopId && <Shop /> }
{ params.taskId && <List /> }
</div>
Just a thought.. I think there are several ways to augment this to handle more complex scenarios but this would definitely work for what you've specified in the question (for the most part).
Update: Above is 'hardcoded', but of course you do not need to write out every combination by hand. This is what loops are for.
import * as routeComponents from './routeComponents'
<Router history={browserHistory}>
{ routeComponents.map(r => <Route {...r} />) }
</Router>
let App = ({ params }) =>
<div>
{ routeComponents.reduce((matches, r) => ([
...components,
...(params[r.slug] ? [r.component] : [])
]), [])}
</div>
We have developed our own router Boring Router with "parallel routing" support, though it depends on MobX and works differently with React Router in many ways.
In Boring Router, a parallel route is stored as query string parameter prefixed with _. E.g., /app/workbench?_sidebar=/achievements.
Check out a complete example here.

Resources