Modules: forward declarations

I would like to make use of a component that is defined afterwards. For example:

// TreeItem uses TreeList
module TreeItem = {
  @react.component
  let make = (~item: item) => {
    <>
      <li id=item.id>{React.string(item.name)}</li>
      <TreeList items=item.items />  /* this throws an error */
    </>
  }
}

// and TreeList uses TreeItem
module TreeList = {
  @react.component
  let make = (~items: array<item>) => {
    <ul>
      {items
        ->Belt.Array.map(item => <TreeItem item=item />)
        ->React.array}
    </ul>
  }
}

however the previous code throws the following error:

The module or file TreeList can't be found.

How can I use a component even if it has been defined afterward?

We can use the and keyword to declare “recursive modules”. Found here:

// Recursive modules require signatures.
module type TreeItemType = {
  @react.component
  let make: (~item: item) => React.element
}

module type TreeListType = {
  @react.component
  let make: (~items: array<item>) => React.element
}

// Defines a recursive module.
module rec TreeItem: TreeItemType = {
  @react.component
  let make = (~item: item) => {
    <>
      <li id=item.id>{React.string(item.name)}</li>
      <TreeList items=item.items />
    </>
  }
}

// Thanks to the `and` keyword `TreeItem` knows `TreeList` in advance.
// The `and` keyword can be used in recursive functions as well.
and module TreeList: TreeListType = {
  @react.component
  let make = (~items: array<item>) => {
    <ul>
      {items
        ->Belt.Array.map(item => <TreeItem item=item />)
        ->React.array}
    </ul>
  }
}

Mamita linda!

3 Likes

You can annotate the signatures like normal types:

module rec TreeItem: {
  ...
} = {
  ...
}

and module TreeList: {
  ...
} = {
  ...
}
6 Likes