React-redux-universal-hot-example: Code splitting not working(based on react router's dynamic routes)

Created on 13 Jan 2016  路  14Comments  路  Source: erikras/react-redux-universal-hot-example

Hi!
I'm trying to implement code splitting based on router's dynamic feature(https://github.com/rackt/react-router/blob/master/docs/guides/advanced/DynamicRouting.md) like that

  path: 'course/:courseId',

  getChildRoutes(location, callback) {
    require.ensure([], function (require) {
      callback(null, [
        require('./routes/Announcements'),
        require('./routes/Assignments'),
        require('./routes/Grades'),
      ])
    })
  },

  getIndexRoute(location, callback) {
    require.ensure([], function (require) {
      callback(null, {
        component: require('./components/Index'),
      })
    })
  },

  getComponents(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Course'))
    })
  }

It works fine in this commit(just react-router and basic server render).
https://github.com/EcutDavid/example-react-router-server-rendering-lazy-routes/commit/6e070ee4a573fefe730dec2eadbe6dc38d0e5529

But it not works on my application based on this project.
image
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method ofRoutingContext.

Currently my code like that:
-routes
--index.js

// polyfill webpack require.ensure
if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)

export default {
  component: 'div',
  getChildRoutes(location, cb) {
    require.ensure([], (require) => {
      cb(null, [
        require('./ShareGraphRoute'),
        require('./AppRoute'),
        require('./NotFoundRoute'),
      ])
    })
  }
}

-routes
--ShareGraphRoute

// polyfill webpack require.ensure
if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)

import {
  SHARE_GRAPH_PATH,
} from 'constants/defaults'

export default {
  path: SHARE_GRAPH_PATH,
  getComponent(location, cb) {
    require.ensure([], (require) => {
      cb(null, require('components/ShareGraph'))
    })
  },
}

Seems need change in server.js or I made some mistakes, any hint?
thanks!!

Most helpful comment

You are welcome :)

All 14 comments

@EcutDavid have you got polifyll in your first file? What babel are you using? If you are using babel6, you need to specify default to use default export in require like this:

...
require('./routes/Announcements').default,
...

is your source hosted somewhere? I can look and help you :)

@svrcekmichal Thanks!

it's not caused by no use .default.

Sorry, it's company's private project, can not display the whole application code....
But it's build based on this repo.
Here is the dependencies: use babel 5.8

"dependencies": {
    "babel-core": "5.8.x",
    "babel-runtime": "^5.8.34",
    "bluebird": "2.10.2",
    "classnames": "^2.1.2",
    "compression": "^1.6.0",
    "d3": "3.5.6",
    "es6-promise": "3.0.2",
    "express": "^4.13.3",
    "falcor": "^0.1.13",
    "falcor-express": "^0.1.2",
    "falcor-http-datasource": "^0.1.2",
    "falcor-router": "^0.2.9",
    "geoip-lite-country": "^1.1.3",
    "history": "1.17.0",
    "hoist-non-react-statics": "^1.0.3",
    "http-server": "^0.7.5",
    "immutable": "^3.7.4",
    "invariant": "^2.2.0",
    "json-loader": "^0.5.2",
    "leaflet": "^0.7.5",
    "lodash": "^3.10.1",
    "mapbox.js": "^2.2.2",
    "match-media": "^0.2.0",
    "numeral": "^1.5.3",
    "piping": "^0.3.0",
    "pretty-error": "^1.2.0",
    "query-string": "^2.4.2",
    "ramda": "^0.18.0",
    "react": "^0.14.2",
    "react-addons-css-transition-group": "^0.14.3",
    "react-addons-test-utils": "^0.14.3",
    "react-autolink-text": "wiredcraft/react-autolink-text",
    "react-dom": "^0.14.2",
    "react-immutable-proptypes": "^1.2.3",
    "react-infinite": "^0.7.3",
    "react-progress": "0.0.9",
    "react-redux": "^4.0.0",
    "react-router": "^1.0.2",
    "react-slider": "^0.6.0",
    "redis": "2.2.5",
    "redux": "^3.0.4",
    "redux-falcor": "^2.3.3",
    "redux-router": "^1.0.0-beta5",
    "redux-thunk": "^0.1.0",
    "reselect": "^2.0.0",
    "rx": "3.1.1",
    "serialize-javascript": "^1.1.2",
    "serve-favicon": "^2.3.0",
    "serve-static": "^1.10.0",
    "topojson": "^1.6.19",
    "transit-js": "^0.8.837",
    "warning": "^2.1.0",
    "webpack-isomorphic-tools": "^0.9.3"
  },
  "devDependencies": {
    "align-text": "^0.1.3",
    "autoprefixer-loader": "^3.1.0",
    "babel-eslint": "4.1.x",
    "babel-loader": "^5.4.0",
    "babel-plugin-react-transform": "^1.1.1",
    "balanced-match": "^0.2.0",
    "benchpress": "2.0.0-alpha.46",
    "better-npm-run": "0.0.2",
    "concat-map": "0.0.1",
    "concurrently": "^0.1.1",
    "css-loader": "^0.23.0",
    "cssnext-loader": "^1.0.1",
    "eslint": "^1.10.2",
    "eslint-plugin-react": "^3.11.1",
    "extract-text-webpack-plugin": "^0.8.2",
    "file-loader": "^0.8.5",
    "fs-extra": "^0.22.1",
    "gh-pages": "0.3.1",
    "glob": "^5.0.14",
    "jasmine": "^2.3.1",
    "node-bourbon": "^4.2.3",
    "node-sass": "^3.4.2",
    "normalize.css": "3.0.3",
    "phantomjs": "^1.9.18",
    "phantomjs-polyfill": "0.0.1",
    "protractor": "3.0.0",
    "react-hot-loader": "^1.3.0",
    "react-transform-hmr": "^1.0.1",
    "redux-devtools": "^2.1.5",
    "reflect-metadata": "^0.1.0",
    "sass-loader": "^3.1.2",
    "strip-loader": "^0.1.0",
    "style-loader": "0.12.3",
    "traceur": "0.0.91",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.9",
    "webpack-dev-middleware": "^1.4.0",
    "webpack-hot-middleware": "^2.5.0",
    "yargs": "^3.17.1"
  }

The error prompted after change below:

+// polyfill webpack require.ensure
+if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)
+
 import {
   SHARE_GRAPH_PATH,
 } from 'constants/defaults'
-import ShareGraph from 'components/ShareGraph'

 export default {
   path: SHARE_GRAPH_PATH,
-  component: ShareGraph
+  getComponent(location, cb) {
+    require.ensure([], (require) => {
+      cb(null, require('components/ShareGraph'))
+    })
+  },
 }

But
-routes
--index.js mentioned before, works fine.

@EcutDavid what is output of this?

console.log(require('components/ShareGraph'))

can you compare it with original import?

Here is the output:

{ __esModule: true,
[1]   ShareGraph: 
[1]    { [Function: ShareGraph]
[1]      propTypes: 
[1]       { chart: [Function: bound checkType],
[1]         location: [Function: bound checkType],
[1]         selectedIndicator: [Object],
[1]         selectedView: [Function: bound checkType],
[1]         switchUserCountry: [Function: bound checkType],
[1]         title: [Function: bound checkType],
[1]         updateSelectionByQuery: [Function: bound checkType] } },
[1]   default: 
[1]    { [Function: Connect]
[1]      displayName: 'Connect(ShareGraph)',
[1]      WrappedComponent: { [Function: ShareGraph] propTypes: [Object] },
[1]      contextTypes: { store: [Object] },
[1]      propTypes: { store: [Object] } } }

after see the output, I change cb(null, require('components/ShareGraph')) to cb(null, require('components/ShareGraph').default)
It works!!!!

Why there is no need to add .default in there???

// polyfill webpack require.ensure
if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)

export default {
  component: 'div',
  getChildRoutes(location, cb) {
    require.ensure([], (require) => {
      cb(null, [
        require('./ShareGraphRoute'),
        require('./AppRoute'),
        require('./NotFoundRoute'),
      ])
    })
  }
}

@svrcekmichal
Thank you very much!
Could you give me some reference about when to use the default and when not???

Thanks again!

@EcutDavid well, you need to use .default when you need to use old require with es6 default exported content. So it really depends on type of export you use, I can't tell you why the second require works, but you can always try console.log to see output of require.

Or simply paste here source of ShareGraphRoute and ShareGraph component and console.log of their require, so we can see the difference

ShareGraph

/* global __CLIENT__ */
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import numeral from 'numeral'
import _ from 'lodash'

import * as RouteActions from 'actions/RouteActions'
import * as UserActions from 'actions/UserActions'
import {
  APP_PATH,
  VIEW_CHART,
  VIEW_MAP,
} from 'constants/defaults'
import * as propTypes from 'constants/propTypes'
import {
  indicatorChartSelector,
  locationsChartSelector,
} from 'selectors/dataSelectors'
import {
  selectedFeaturedIndicatorMetaSelector,
  selectedIndicatorMetaSelector,
  selectedIndicatorTypeSelector,
} from 'selectors/indicatorSelectors'
import MapComponent from './MapComponent'
import Chart from './Chart'
import { wbNormal } from 'constants/textFormat'


export class ShareGraph extends Component {

  static propTypes = {
    chart: propTypes.chart.isRequired,
    location: PropTypes.object.isRequired,
    selectedIndicator: PropTypes.string,
    selectedView: PropTypes.string.isRequired,
    switchUserCountry: PropTypes.func.isRequired,
    title: PropTypes.string.isRequired,
    updateSelectionByQuery: PropTypes.func.isRequired,
  }

  componentWillMount() {
    const { location, updateSelectionByQuery, switchUserCountry } = this.props

    updateSelectionByQuery({}, location.query)

    if (__CLIENT__) {
      const xhr = new XMLHttpRequest()
      xhr.open('GET', '/geo/ip', true)
      xhr.onload = function (e) {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            switchUserCountry(JSON.parse(xhr.response).country)
          } else {
            console.error(JSON.parse(xhr.response).error)
          }
        }
      }

      xhr.onerror = function (e) {
        console.error('onerror', xhr.statusText)
      }

      xhr.send()
    }
    numeral.language('wbNormal', wbNormal)
    numeral.language('wbNormal')
  }

  render() {
    const {
      chart: { data, domain },
      location,
      selectedIndicator,
      selectedView,
      title,
    } = this.props

    const onlyLocations = !selectedIndicator
    const url = APP_PATH + location.search

    return (
      <article className='card indicator iframe'>
        <h1>{ title }</h1>
        { (selectedView === VIEW_CHART || onlyLocations) && (
          <Chart
            data={data}
            domain={domain}
            onSelect={() => {}}
            options={{ hideSpinner: true }}
          />
        )}

        { (!onlyLocations && selectedView === VIEW_MAP) && (
          <MapComponent />
        )}

        <span className='footer'>
          Data from <a href={url} target='_blank'>World Bank</a>
        </span>
      </article>
    )
  }
}

function mapStateToProps (state, props) {
  const { view, router } = state
  const onlyLocationsSelected = props.location.query.indicators === undefined

  const chart = onlyLocationsSelected ?
    locationsChartSelector(state) :
    indicatorChartSelector(state)

  const indicator = onlyLocationsSelected ?
    selectedFeaturedIndicatorMetaSelector(state) :
    selectedIndicatorMetaSelector(state)

  return {
    chart,
    selectedView: view.get('selectedView'),
    location: router.location,
    selectedIndicator: selectedIndicatorTypeSelector(state),
    title: _.get(indicator, 'fullname', ''),
  }
}

function mapDispatchToProps (dispatch) {
  return bindActionCreators({
    updateSelectionByQuery: RouteActions.updateSelectionByQuery,
    switchUserCountry: UserActions.switchUserCountry,
  }, dispatch)
}

export default connect(mapStateToProps, mapDispatchToProps)(ShareGraph)

ShareGraphRoute

// polyfill webpack require.ensure
if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)

import {
  SHARE_GRAPH_PATH,
} from 'constants/defaults'

export default {
  path: SHARE_GRAPH_PATH,
  getComponent(location, cb) {
    require.ensure([], (require) => {
      cb(null, require('components/ShareGraph').default)
    }, 'ShareGraph')
  },
}

looks like the same...

@EcutDavid and console.log of their requires?

@EcutDavid from what i can tell ShareGraphRoute will be exported as object, with keys
default,path and getComponent which can work, because you have path and getcomponent available. But in first ShareGraph you only export default and ShareGraph, but router don't know how to use this object, it's not react component

Route:
{ path: '/share/widget',
getComponent: [Function: getComponent] }

Component:
{ __esModule: true,
[1] ShareGraph:
[1] { [Function: ShareGraph]
[1] propTypes:
[1] { chart: [Function: bound checkType],
[1] location: [Function: bound checkType],
[1] selectedIndicator: [Object],
[1] selectedView: [Function: bound checkType],
[1] switchUserCountry: [Function: bound checkType],
[1] title: [Function: bound checkType],
[1] updateSelectionByQuery: [Function: bound checkType] } },
[1] default:
[1] { [Function: Connect]
[1] displayName: 'Connect(ShareGraph)',
[1] WrappedComponent: { [Function: ShareGraph] propTypes: [Object] },
[1] contextTypes: { store: [Object] },
[1] propTypes: { store: [Object] } } }

@svrcekmichal
The component generate a filed __esModule

So there it is, your component exported ShareGraph and default and you have to tell router, which of them to use, the overall export is not react component

Got it, that's make sense.
@svrcekmichal you help me a lot, thanks!

You are welcome :)

Thanks, this helped me track down a bug that was ruining my day!!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ansonla3 picture ansonla3  路  4Comments

bdsabian picture bdsabian  路  6Comments

sauravskumar picture sauravskumar  路  6Comments

krishnasaga picture krishnasaga  路  3Comments

glennr picture glennr  路  6Comments