Purescript - Parent - Child Components in Halogen

Posted on March 11, 2020

Parent-Child component communication is already part of the halogen examples and guide. This entry is just my own understanding of how Parent-Child component communication works.

Rendering

Let’s start with rendering a child component inside a parent component. In other frameworks this is called transclusion/multitransclusion (angular), slots (vue), and in react I think it’s called component composition. In Halogen, this can be acheived with what they call Child Slots. The concept is pretty similar to existing front end frameworks.

Child component

Let’s define the child component.

Parent component

Now, the parent component with the slot and the child component.

This will render the child component in the parent component.

Child to Parent component communication

The key to child-parent component communication is acheived with the use of raise. This is how the child component can send messages that the parent can then listen to. According to the docs this is how raise is defined.

Here’s the child component again, using raise in the handleActions function.

And here’s how the parent component can listen to that output message. You’ll notice that the constructor of Action has Child.OutputMessage input. This input is handled in the handleAction function.

Then handleAction function then matches HandleChildMsg Child.OutputMessage and modifies the state of the parent component.

Parent to Child component communication

Now for the parent to send messages to the child component. Let’s modify the child component to receive messages.

First create the type of Input.

This will replace the i type variable in the component function.

The key to receiving messages/input from the parent component is by providing the receive field in H.defaultEval with an action. According to the source, receive is defined like this

So we provide the receive field with \input -> Just $ HandleReceiveMessage input, for that extra FP points we’ll provide it wit this this point free function Just <<< HandleRecieveMessage. This action will then be handled in the handleAction function and update the state as desired.

Now, let’s make the modification the parent component.

First, let’s change the input parameter in the child slot from unit to { messageFromParent: st.messageToChild } in the render function. The field messageFromParent is what the child component is expecting and st.messageToChild is the piece of state from the parent component that we’ll send to the child component.

module Component.Parent where

import Prelude
import Data.Symbol ( SProxy (..) )
import Data.Maybe ( Maybe(..) )
-- Internal
import Component.Child as Child
-- Halogen
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE

type ChildSlots =
  ( child :: Child.Slot
  )

data Action
  = HandleChildMsg Child.OutputMessage
  | HandleInput String
  | SendMessageToChild

type State =
  { clickCount :: Int
  , message :: String
  , messageToChild :: String
  }

component :: forall q i o m. H.Component HH.HTML q i o m
component =
  H.mkComponent
  { initialState: const { clickCount: 0, message: "", messageToChild: "" }
  , render
  , eval: H.mkEval $ H.defaultEval
    { handleAction = handleAction }
  }

render :: forall m. State -> H.ComponentHTML Action ChildSlots m
render st =
  HH.div_
  [ HH.h1_ [ HH.text "Parent" ]
  , HH.div_
    [ HH.input
      [ HE.onValueInput ( Just <<< HandleInput )
      ]
    , HH.button
      [ HE.onClick ( const $ Just SendMessageToChild )
      ]
      [ HH.text "Send message to child component" ]
    ]
  , HH.h5_ [ HH.text "Clicks from child component" ]
  , HH.div_ [ HH.text $ show st.clickCount ]
  , HH.hr []
  , HH.slot ( SProxy :: _ "child" ) unit Child.component { messageFromParent: st.messageToChild } ( Just <<< HandleChildMsg )
  ]

handleAction :: forall o m. Action -> H.HalogenM State Action ChildSlots o m Unit
handleAction = case _ of
  HandleChildMsg ( Child.ButtonClick n ) -> do
    count <- H.gets _.clickCount
    H.modify_ _ { clickCount = count + n }

  HandleInput str ->
    H.modify_ _ { message = str }

  SendMessageToChild -> do
    msg <- H.gets _.message
    H.modify_ _ { messageToChild = msg }

The parent component is first updating the message field in the state based on the input element. Then, when the button is clicked it will update messageToChild with the value from message.

The project and it’s entirety is here. Clone it and play with it!

Sources

Halogen Example by the Halogen team

Halogen Guide by the Halogen team

Realworld Halogen Example