I think this would be a good issue to open in my learn-halogen repo. Would you mind opening an issue there?
The code below hasn’t been checked for compiler errors, but should give you a good enough idea for how to do it.
Figure 1
Questions:
Is this communication unidirectional (e.g. A -> C) or bidirectional (e.g. A <-> C)? If the former, use Query. If the latter, you might want a Bus or something.
While Component A can technically talk to Component C by using either Query or Input, you probably want to use Query.
If using Input, A would update its state, which causes A to re-render itself, thereby passing an Input value into B. B would handle that value via its Receive action. B could then update its state, which rerenders C, which receives the new Input value via its Receive action. C then has to know that the input value is a special message from A and thus it should do something.
The downsides of this approach is that A and B’s state has to be updated and view has to be rerendered in order to communicate with C. If that fits your domain situation, then that will work. Otherwise, it’s probably not what you want.
If using Query, neither A nor B’s state needs to change. Rather, clicking on a button in A could trigger a command from A to C through B. Here’s how it would work.
B would have a Query that allows it to send a Query to C. For example, Query a = ForwardQuery ChildQuery a. When B needs to handle ForwardQuery, it simply does
case _ of
ForwardQuery chlidQuery next -> do
void $ H.query childSlot childIndex $ H.tell childQuery
pure (Just next)
Then A would wrap a C query inside the B’s forwarding query and query B with the forwarding query. For example, A might run the following code inside its handleAction/handleQuery function:
handleAction = case _ of
SendCCommand -> do
void $ H.query slot index $ H.tell $ ForwadQuery ChildQuery
Below is my current guess. I’d imagine others have a better/fuller answer.
Not recommended: B raises a message to A, who sends a query into C, who possibly sends back information. A then sends a query into B after C responds to A’s initial query.
A creates two Effect.Aff.Buses or Concurrent.Queues and passes both into B and C as part of their Input. B passes into C’s bus/queue two things: 1) the values that C needs in order to do what B desires and 2) the rest of B’s computation once C has done that (if needed). Assuming C has forked a fiber that will read C’s bus/queue, B can send in those two values, C will execute its part and send the necessary data into the B computation. Likewise if C needs B to do something.
Use the ContT monad somehow…? I’m don’t think Halogen currently allows this because it’s underlying execution is using the Free monad.
Thanks your comment @JordanMartinez !
For figure 1, I was thinking both unidirectional and bidirectional and my naive idea would be use B is bridge which I think is not ideal.
Those could be common use case and wondering how people solve them in real world.
I’d say for all of them, “it depends”. But I generally do the simplest thing, which means just using queries and output messages.
Fig 1: Component B would pass queries and messages between A and C.
Fig 2: Component A would pass queries and messages between B and C.
Fig 3: User Profile Form raises a message to Main which raises a message to A which tells Header Information to update as necessary.
This approach does result in some boilerplate, since you need to manually bubble messages / forward queries, but one positive aspect is it makes it very easy to follow the flow of things.
I don’t resort to Bus and the like unless there are many components that need to talk to a single one, or vice versa.
edit: depending on what is being communicated, inputs may also be suitable for some of the cases where I said “query” here. It depends where you want to manage the state changes. Also I only use inputs for “continuous” values and don’t attempt to “gate” or trigger things based on changes in an input.