This hook will find an HTMLElement in the DOM and render the given JSX as its child. Portals are useful for layering UI elements on top (popovers, modals, toasts, tooltips, etc).

Use it like this:

module MyComponent where

import Prelude
import React.Basic.Hooks (Component, component)
import Hooks.UseRenderInPortal (useRenderInPortal)
import React.Basic.Hooks as React
import React.Basic.DOM (text, div_) as R

mkComponent  Component {}
mkComponent = component "MyComponent" \_ ->
  renderInPortal ← useRenderInPortal "my-portal"
  pure $ renderInPortal $ R.div_ [ R.text "Hello Portal" ]
module Hooks.UseRenderInPortal
  ( UseRenderInPortal(..)
  , useRenderInPortal
  ) where

import Prelude

import Data.Maybe (Maybe, Maybe(..), isNothing)
import Data.Newtype (class Newtype)
import React.Basic.DOM (createPortal)
import React.Basic.Hooks as React
import Web.DOM (Element)
import Web.HTML (window)
import Web.HTML.Window (document)
import Web.HTML.HTMLDocument (toNonElementParentNode) as HTMLDocument
import Web.DOM.NonElementParentNode (getElementById)
import React.Basic.Hooks (UseEffect, UseState, useEffectOnce)
import React.Basic.Hooks.Internal (Hook, coerceHook)
import Data.Foldable (foldMap)
import Effect (Effect)

newtype UseRenderInPortal hooks = UseRenderInPortal
  (UseEffect Unit (UseState (Maybe Element) hooks))

derive instance Newtype (UseRenderInPortal hooks) _

useRenderInPortal  String  Hook UseRenderInPortal (JSX  JSX)
useRenderInPortal portalId = coerceHook
  portalOrNot /\ setPortal ← React.useState' Nothing
  let renderInPortal jsx = portalOrNot # foldMap (createPortal jsx)
  useEffectOnce do
    when (portalOrNot # isNothing) do
      findElementByIdInDocument portalId >>= setPortal
  pure renderInPortal

findElementByIdInDocument  String  Effect (Maybe Element)
findElementByIdInDocument id = do
  doc ← window >>= document
  let node = HTMLDocument.toNonElementParentNode doc
  getElementById id node