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.
module Component.Child where
import Prelude
-- Halogen
import Halogen as H
import Halogen.HTML as HH
type Slot = forall query. H.Slot query Void Unit
component :: forall q i o m. H.Component HH.HTML q i o m
=
component
H.mkComponent: identity
{ initialState
, render: H.mkEval H.defaultEval
, eval
}
render :: forall state action m. state -> H.ComponentHTML action () m
=
render _
HH.h2_"Child"
[ HH.text ]
Parent component
Now, the parent component with the slot and the child component.
module Component.Parent where
import Prelude
import Data.Symbol ( SProxy (..) )
-- Internal
import Component.Child as Child
-- Halogen
import Halogen as H
import Halogen.HTML as HH
type ChildSlots =
child :: Child.Slot
(
)
component :: forall q i o m. H.Component HH.HTML q i o m
=
component
H.mkComponent: identity
{ initialState
, render: H.mkEval H.defaultEval
, eval
}
render :: forall action state m. state -> H.ComponentHTML action ChildSlots m
=
render _
HH.div_"Parent" ]
[ HH.h1_ [ HH.text
, HH.hr []SProxy :: _ "child" ) unit Child.component {} absurd
, HH.slot ( ]
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.
raise :: forall s f g p o m. o -> HalogenM s f g p o m Unit
Here’s the child component again, using raise
in the
handleActions
function.
module Component.Child where
import Prelude
import Data.Maybe ( Maybe(..) )
-- Halogen
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
type Slot = forall query. H.Slot query OutputMessage Unit
data Action = HandleClick
data OutputMessage = ButtonClick Int
component :: forall q m. H.Component HH.HTML q Unit OutputMessage m
=
component
H.mkComponent: identity
{ initialState
, render: H.mkEval $ H.defaultEval
, eval= handleAction }
{ handleAction
}
render :: forall state m. state -> H.ComponentHTML Action () m
=
render _
HH.div_"Child" ]
[ HH.h2_ [ HH.text
, HH.buttonconst $ Just Click )
[ HE.onClick (
]"Send clicks to parent"]
[ HH.text
]
handleAction :: forall m. Action -> H.HalogenM Unit Action () OutputMessage m Unit
= case _ of
handleAction HandleClick -> do
ButtonClick 1 ) H.raise (
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.
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
type ChildSlots =
child :: Child.Slot
(
)
data Action
= HandleChildMsg Child.OutputMessage
type State =
clickCount :: Int
{
}
component :: forall q i o m. H.Component HH.HTML q i o m
=
component
H.mkComponent: const { clickCount: 0 }
{ initialState
, render: H.mkEval $ H.defaultEval
, eval= handleAction }
{ handleAction
}
render :: forall m. State -> H.ComponentHTML Action ChildSlots m
=
render st
HH.div_"Parent" ]
[ HH.h1_ [ HH.text $ show st.clickCount ]
, HH.div_ [ HH.text
, HH.hr []SProxy :: _ "child" ) unit Child.component unit ( Just <<< HandleChildMsg )
, HH.slot (
]
handleAction :: forall o m. Action -> H.HalogenM State Action ChildSlots o m Unit
= case _ of
handleAction HandleChildMsg ( Child.ButtonClick n ) -> do
<- H.gets _.clickCount
count = count + n } H.modify_ _ { clickCount
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
.
type Input =
messageFromParent :: String
{ }
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
receive :: input -> Maybe action
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.
module Component.Child where
import Prelude
import Data.Maybe ( Maybe(..) )
-- Halogen
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
type Slot = forall query. H.Slot query OutputMessage Unit
data Action
= HandleClick
| HandleReceiveMessage Input
type State =
message :: String
{
}
data OutputMessage = ButtonClick Int
type Input =
messageFromParent :: String
{
}
component :: forall q m. H.Component HH.HTML q Input OutputMessage m
=
component
H.mkComponent: const $ { message: "" }
{ initialState
, render: H.mkEval $ H.defaultEval
, eval= handleAction
{ handleAction = ( Just <<< HandleReceiveMessage )
, receive
}
}
render :: forall m. State -> H.ComponentHTML Action () m
=
render st
HH.div_"Child" ]
[ HH.h2_ [ HH.text
, HH.buttonconst $ Just HandleClick )
[ HE.onClick (
]"Send clicks to parent"]
[ HH.text "Message from parent" ]
, HH.h5_ [ HH.text .message
, HH.text st
]
handleAction :: forall m. Action -> H.HalogenM State Action () OutputMessage m Unit
= case _ of
handleAction HandleClick -> do
ButtonClick 1 )
H.raise (
HandleReceiveMessage str ->
= str.messageFromParent } H.modify_ _ { message
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: const { clickCount: 0, message: "", messageToChild: "" }
{ initialState
, render: H.mkEval $ H.defaultEval
, eval= handleAction }
{ handleAction
}
render :: forall m. State -> H.ComponentHTML Action ChildSlots m
=
render st
HH.div_"Parent" ]
[ HH.h1_ [ HH.text
, HH.div_
[ HH.inputJust <<< HandleInput )
[ HE.onValueInput (
]
, HH.buttonconst $ Just SendMessageToChild )
[ HE.onClick (
]"Send message to child component" ]
[ HH.text
]"Clicks from child component" ]
, HH.h5_ [ HH.text $ show st.clickCount ]
, HH.div_ [ HH.text
, HH.hr []SProxy :: _ "child" ) unit Child.component { messageFromParent: st.messageToChild } ( Just <<< HandleChildMsg )
, HH.slot (
]
handleAction :: forall o m. Action -> H.HalogenM State Action ChildSlots o m Unit
= case _ of
handleAction HandleChildMsg ( Child.ButtonClick n ) -> do
<- H.gets _.clickCount
count = count + n }
H.modify_ _ { clickCount
HandleInput str ->
= str }
H.modify_ _ { message
SendMessageToChild -> do
<- H.gets _.message
msg = msg } H.modify_ _ { messageToChild
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