TL;DR Here is a repository with a working example. :)
In my posts of the last weeks I told you about two things. How to improve your React code-base with higher order components (HOC) and how to make parts of your single page application load on-demand with Webpack 2.
In this post I will show you how to merge these into one.
Why?
Because it's cool!
And it lets you save even more bandwidth.
You could create modals, pop-ups, context-menus, notifications, dialogs and what-not and the needed code gets loaded when the feature is needed.
I'm not telling this is the silver bullet, you should check with your users which stuff isn't needed too often or too heavy weight, and only move these into other chunks, otherwise your users will end up with loading indicators for every click they do.
Anyway, lets get started!
I will refer to code in an example repository I made.
First I created a <BigList>
component, which is basically an unsorted list with about 1000 list items, this will simulate a bit of loading time.
Second I created a HOC called lazify
, which is used to create a wrapper for our <BigList>
.
Lazify differs from regular HOCs, because it can't get a reference to the constructor of the component it wraps. Because, well, it's not loaded right now.
We also can't give it simply the path to the file with the definition of our target component.
Why not?
Because Webpack searches the code for System.import(...)
and creates new chunks depending on the path argument. If it finds something like System.import(variable)
it can't make any educated guesses about "which" files it should move to another chunk.
Okay, so we have to give lazify System.import('/path/to/our/Component')
?
No, because this would be executed when the lazify
function is called and lazify
would get the promise, that resolves to the module.
So what do we need to pass to lazify
to get things running, but only if the lazified version of our component is rendered?
Well, the best thing you could use in any place, a function.
A function that will call System.import
when it's needed.
So instead of creating the lazy loaded version like this:
import BigList from 'BigList'
import lazify 'lazify'
export default lazify(BigList)
which would load everything statically and not provide us any benefit.
We the file needs to look like this:
import lazify from 'lazify'
export default lazify(() => System.import('BigList'))
Now lazify
is able to create our wrapping component (WC) and give it access to the function that will load the right import.
import React from 'react'
export default
function lazify(load) {
return class LazyComponent extends React.Component {
constructor() {
super()
this.state = {Component: null}
}
componentDidMount() {
load().then(m => this.setState({Component: m['default']}))
}
render() {
const {props} = this
const {Component} = this.state
return Component? <Component {...props}/> : <span>Loading...</span>
}
}
}
The new component will have a Component
state, initialized to null
.
The first time it's mounted, it will render a loading indication, then componentDidMount
will be called by React and our WC will call the load
function we passed to lazify
earlier.
This function calls System.import
and will return its promise. The promise will resolve to our loaded module and this should export a React component as default.
When default export, the real component (RC) we really want to render is stored in the state of the WC, the WC will render again, this time with the RC and pass its props
down to it, so the parent of WC won't be the wiser about what happened.
My idea about the directory structure here was to store the RC into its own folder in an index.js
and the WC in lazy.js
.
So when you use it you are aware that it's a lazy version.
import BigList from 'BigList'
import LazyList from 'BigList/lazy'
Top comments (0)