Skip to content

πŸ”₯ A higher order module for loading dynamic components with ReasonReact.

License

Notifications You must be signed in to change notification settings

thangngoc89/reason-loadable

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Summary

Build Status NPM license

πŸ”₯ A higher order module for loading dynamic components with ReasonReact.

Installation

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.

Motivation

This project is strongly inspired from "react-loadable", the goal is ultimately to achieve the same functionality in pure Reason.

Common problems

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. ❌

Support

Example

Basic example

  1. 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>
};
  1. 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);
  1. 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>
};
  1. 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>
};
  1. 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>
};
  1. 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.

API

ReLoadable

Configuration: { module type t; }

Configuration store importable module type.

ReLoadable.WithRender: Configuration => LoadableComponent

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.

ReLoadable.WithChildren: Configuration => LoadableComponent

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.

DynamicImport.import: string => Js.Promise.t(DynamicImport.importable('a))

Import dynamic module ("bs.js" compiled file).

Options

You have to provide fetch and render/children function, rest are optionnal.

fetch: unit => Js.Promise.t(DynamicImport.importable((module Configuration.t)))
render | children: (module Configuration.t) => ReasonReact.reactElement
onFail: string => ReasonReact.reactElement (optionnal, by default use ReasonReact.nullElement)
onLoading: string => ReasonReact.reactElement (optionnal, by default use ReasonReact.nullElement)
delay: int (optionnal, by default use 200ms)

About

πŸ”₯ A higher order module for loading dynamic components with ReasonReact.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 63.6%
  • OCaml 36.1%
  • Shell 0.3%