Search code examples
purescripthalogen

Purescript Halogen: can I request the state of child component that is also a parent?


If I have a grandparent, a child component, and a grandchild component, can the grandparent request the state of the child? I've tried using "request" like here but the types don't match up when you're requesting the state of a child that also has a child of its own. The example in the guide works fine when I'm requesting the state of a child that does not have children.

The error is:

Could not match type

  Query

with type

  Coproduct (Coproduct Query (ChildF AnswerSlot Query)) Query

Solution

  • Yes, definitely. You're probably just missing a left in the query to the child - that's required as the child's query algebra will be of the form Coproduct f (ChildF p f') with it having its own child.

    Correspondingly, you can query the grandchild from the grandparent too, by using right and constructing a ChildF with the appropriate values for the grandchild.

    I've put together a contrived example of querying both child and grandchild that will hopefully make things clearer:

    module Main where
    
    import Prelude
    
    import Data.Functor.Coproduct (Coproduct, left, right)
    import Data.Maybe (Maybe(..), fromMaybe)
    
    import Debug.Trace (traceA) -- from purescript-debug
    
    import Halogen as H
    import Halogen.HTML as HH
    
    --------------------------------------------------------------------------------
    
    type GrandState = Unit
    data GrandQuery a = AskGrandChild (String -> a)
    
    grandchild :: forall g. H.Component GrandState GrandQuery g
    grandchild = H.component { render, eval }
      where
      render :: GrandState -> H.ComponentHTML GrandQuery
      render _ = HH.div_ []
      eval :: GrandQuery ~> H.ComponentDSL GrandState GrandQuery g
      eval (AskGrandChild k) = pure $ k "Hello from grandchild"
    
    --------------------------------------------------------------------------------
    
    type ChildState = Unit
    data ChildQuery a = AskChild (String -> a)
    type GrandSlot = Unit
    
    type ChildState' g = H.ParentState ChildState GrandState ChildQuery GrandQuery g GrandSlot
    type ChildQuery' = Coproduct ChildQuery (H.ChildF GrandSlot GrandQuery)
    
    child :: forall g. Functor g => H.Component (ChildState' g) ChildQuery' g
    child = H.parentComponent { render, eval, peek: Nothing }
      where
      render :: ChildState -> H.ParentHTML GrandState ChildQuery GrandQuery g GrandSlot
      render _ = HH.slot unit \_ -> { component: grandchild, initialState: unit }
      eval :: ChildQuery ~> H.ParentDSL ChildState GrandState ChildQuery GrandQuery g GrandSlot
      eval (AskChild k) = pure $ k "Hello from child"
    
    --------------------------------------------------------------------------------
    
    type ParentState = Unit
    data ParentQuery a = Something a
    type ChildSlot = Unit
    
    type ParentState' g = H.ParentState ParentState (ChildState' g) ParentQuery ChildQuery' g ChildSlot
    type ParentQuery' = Coproduct ParentQuery (H.ChildF ChildSlot ChildQuery')
    
    parent :: forall g. Functor g => H.Component (ParentState' g) ParentQuery' g
    parent = H.parentComponent { render, eval, peek: Nothing }
      where
      render :: ParentState -> H.ParentHTML (ChildState' g) ParentQuery ChildQuery' g ChildSlot
      render _ = HH.slot unit \_ -> { component: child, initialState: H.parentState unit }
      eval :: ParentQuery ~> H.ParentDSL ParentState (ChildState' g) ParentQuery ChildQuery' g ChildSlot
      eval (Something next) = do
    
        -- note the `left` here
        childAnswer <- H.query unit $ left $ H.request AskChild
        traceA $ fromMaybe "child not found" $ childAnswer
    
        grandAnswer <- H.query unit $ right $ H.ChildF unit $ H.request AskGrandChild
        traceA $ fromMaybe "grandchild not found" $ grandAnswer
    
        pure next