π₯ A higher order module for loading dynamic components with ReasonReact.
npm install reason-loadable bs-dynamic-import --save
Then add it to "bsconfig.json" :
"bs-dependencies": [
"bs-dynamic-import",
"reason-loadable"
]
You can now use "ReLoadable" & "DynamicImport" module.
About "DynamicImport", you can take a look at the documentation for more information.
This project is strongly inspired from "react-loadable", the goal is ultimately to achieve the same functionality in pure Reason.
Some of the most common problematic patterns that were covered include :
- Import ReasonReact component dynamically. βοΈ
- Support function as children / render prop. βοΈ
- Minimal SSR support. βοΈ
- Import multiple component dynamically in parallel. β
- Full SSR support (preloadAll, preload, preloadReady). β
- Retry. β
-
Server-side (Node.js) : Node.js doesn't support dynamic import, you should use Babel with "babel-plugin-dynamic-import-node". #example
-
Client-side (web) : you should use a bundler (Webpack 4 and Parcel support dynamic import with zero-configuration, Rollup require experimental flag). #example
- Create a ReasonReact component.
/* HelloWorld.re */
let component = ReasonReact.statelessComponent("HelloWorld");
let make = (~name, _children) => {
...component,
render: _self =>
<h1> (ReasonReact.stringToElement("Hello world " ++ name)) </h1>
};
- Create type-safe dynamic component with "ReLoadable" module.
/* LazyHelloWorld.re */
module Config = {
module type t = (module type of HelloWorld);
};
/* Include component : LazyHelloWorld is now a component. */
include ReLoadable.WithRender(Config);
- Render dynamic component anywhere in your code like any component, fetch with "DynamicImport" module & render.
/* App.re */
let component = ReasonReact.statelessComponent("App");
let make = _children => {
...component,
render: _self =>
<div>
<LazyHelloWorld
fetch=(() => DynamicImport.import("./HelloWorld.bs.js"))
render=(((module HelloWorld)) => <HelloWorld name="Zeus" />)
/>
</div>
};
- We can add optionnal parameters (loading component, failed component and delay).
/* App.re */
let component = ReasonReact.statelessComponent("App");
let make = _children => {
...component,
render: _self =>
<div>
<LazyHelloWorld
fetch=(() => DynamicImport.import("./HelloWorld.bs.js"))
render=(((module HelloWorld)) => <HelloWorld name="Zeus" />)
onLoading=(() => <p>(ReasonReact.stringToElement("Loading ..."))</p>)
onFail=((_error) => <p>(ReasonReact.stringToElement("Oops, something goes wrong !"))</p>)
delay=3000
/>
</div>
};
- You might be thinking that this is cool but a bit ugly ... it would be inconvenient to have to pass props like "fetch", "onLoading", "onFail", "delay", everytime we want to load "HelloWorld" module dynamically. It could be nice if we can reduce boilerplate. So in this case, instead of using "include", letβs define a new make function who will create pre-filled component.
/* LazyHelloWorld.re */
module Config = {
module type t = (module type of HelloWorld);
};
module Loadable = ReLoadable.WithRender(Config);
/* Forward props. */
let make = (~name, _children) =>
Loadable.make(
~fetch=() => DynamicImport.import("./HelloWorld.bs.js"),
~render=((module HelloWorld)) => <HelloWorld name />,
~onLoading=() => <p>(ReasonReact.stringToElement("Loading ..."))</p>,
~onFail=_error => <p>(ReasonReact.stringToElement("Oops, something goes wrong !"))</p>,
~delay=3000,
[||]
);
/* App.re */
let component = ReasonReact.statelessComponent("App");
let make = _children => {
...component,
render: _self =>
<div>
<LazyHelloWorld name="Zeus" />
</div>
};
- What about delegate rendering later ? Luckily, we know the solution for this, we can use partial application to fix some of the parameters.
/* LazyHelloWorld.re */
module Config = {
module type t = (module type of HelloWorld);
};
module Loadable = ReLoadable.WithRender(Config);
/* Remove render and props forwarding. */
let make = _children =>
Loadable.make(
~fetch=() => DynamicImport.import("./HelloWorld.bs.js"),
~onLoading=() => <p>(ReasonReact.stringToElement("Loading ..."))</p>,
~onFail=_error => <p>(ReasonReact.stringToElement("Oops, something goes wrong !"))</p>,
~delay=3000,
[||]
);
/* App.re */
let component = ReasonReact.statelessComponent("App");
let make = _children => {
...component,
render: _self =>
<div>
<LazyHelloWorld
render=(((module HelloWorld)) => <HelloWorld name="Zeus" />)
/>
</div>
};
ποΈ Look much better !
Note : "ReLoadable.WithRender" return a childless LoadableComponent, that mean you literally can't use children ([||]) but have to use "render prop". If you are in favor of "function as children", use "ReLoadable.WithChildren".
More example are available in repository.
Configuration store importable module type.
Take Configuration and create LoadableComponent who use "render" prop pattern.
This LoadableComponent should be wrapped into "make" function if you want to pre-fill parameters.
Take Configuration and create LoadableComponent who use "function as children" pattern.
This LoadableComponent should be wrapped into "make" function if you want to pre-fill parameters.
Import dynamic module ("bs.js" compiled file).
You have to provide fetch and render/children function, rest are optionnal.