Is it possible to integrate "react hooks" Component to "Halogen"?

Being led by a hype, I had written some components in Halogen previously. Time has shown though “react-basic-hooks” to be a better choice, because so far it has been resulting in less code and is much easier to maintain/separate/reuse… That’s mainly due to the overhead and error-proneness Halogen causes whenever the question arises about communication between components.

So now I’ve got a mix of Halogen and React Hooks composed via <script> together. Obviously it’s not a good situation, but rewriting the code from Halogen to React will take some time, which I don’t have. So I’m wondering, is it possible to integrate a “React Hooks” Component to a “Halogen”-written DOM?

Example: in the code below there’s a Halogen page and a React Hooks label. Can I integrate the latter to the former?

module Main where

import Prelude

import Effect (Effect)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.VDom.Driver (runUI)
import React.Basic.Hooks (Component, component)
import React.Basic.DOM as R

main :: Effect Unit
main = HA.runHalogenAff do
  body <- HA.awaitBody
  runUI mainComponent unit body

mainComponent :: forall query input output m. H.Component query input output m
mainComponent =
  H.mkComponent
    { initialState: const unit
    , render
    , eval: H.mkEval H.defaultEval
    }

render :: forall m. Unit -> H.ComponentHTML Unit () m
render _ = HH.div_ []

helloLabel :: Component {}
helloLabel = component "Hello" \_ -> React.do
  pure $ R.label { children: [R.text "hello world"] }

Idk how good of a solution it is, but I figured one way might be:

  1. React: create kind of a “main function” for the component (the one one where you call createRoot and renderRoot), which then renders the component (helloLabel in this case).

    Now, this “main function” needs a location to hook into. Use special id for that, e.g. hellolabel

  2. Halogen: add “Initialize” action.

  3. Halogen: add an id (hellolabel in this case) to where you’d want to render react component

  4. Halogen: during the handling of “initialize action” call the react component “main function”

Implementation for the example code:

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Aff.Class (class MonadAff)
import Effect.Class.Console (log)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.Properties as HP
import Halogen.VDom.Driver (runUI)
import React.Basic.DOM as R
import React.Basic.DOM.Client (createRoot, renderRoot)
import React.Basic.Hooks (Component, component)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)

data Action = Initialize

main :: Effect Unit
main = HA.runHalogenAff do
  body <- HA.awaitBody
  runUI mainComponent unit body

mainComponent :: forall query m. MonadAff m => H.Component query Unit Unit m
mainComponent =
  H.mkComponent
    { initialState: const unit
    , render
    , eval: H.mkEval H.defaultEval { handleAction = handleAction
                                   , initialize = Just Initialize}
    }
  where
    render :: Unit -> H.ComponentHTML Action () m
    render _ = HH.div [HP.id "hellolabel"] []

    handleAction :: Action -> H.HalogenM Unit Action () Unit m Unit
    handleAction Initialize = H.liftEffect mainHelloLabel

-------- React component

mainHelloLabel :: Effect Unit
mainHelloLabel = do
  doc <- document =<< window
  root <- getElementById "hellolabel" $ toNonElementParentNode doc
  case root of
    Nothing -> log "Could not find root." *> pure unit
    Just container -> do
      reactRoot <- createRoot container
      comp <- helloLabel
      renderRoot reactRoot (comp {})

helloLabel :: Component {}
helloLabel = component "Hello" \_ -> React.do
  pure $ R.label { children: [R.text "hello world"] }