Shiny R/server -> JS/client OOP with messaging, outputs, and new input-binding

I routinely build dynamic UIs in my Shiny apps via renderUI(), insertUI(), and removeUI(). Those dynamic UI elements will then often contain input elements, which I may then also want to update via updateXYZ() (i.e., really, via sendInputMessage()). For complex data-dependent custom inputs, the pattern will be to insert that input into the DOM, but it won't be useful until it's been updated based on the current state of some data server-side. (In essence, the inserted nascent input element is something of a useless 'shell' until it receives its first update from the server.)

One way to prevent a race condition (where the update happens before the input element is bound) is to listen for the new input via something like:

observeEvent(input$myInput, updateXYZ("myInput", ...))

... whereby the update occurs in a later reactive flush cycle.

In the pursuit of understanding nuance, however, I'm curious if there's any safe way to do this within the same reactive flush cycle.
In particular, I've had trouble finding documentation on any guaranteed ordering of server -> client messages in each reactive flush.
E.g. are output messages sent first, then sendInputMessage()s, then sendCustomMessage()s?
Or is the messaging ordering non-guaranteed?

And likewise is there any guarantee on the ordering of the client-side handling?
I imagine providing strict guarantees client-side is somewhat tricky, since even if a message that adds an element to the DOM is handled first, the input binding's find() callback would now need to run, detect the new input element, run the binding's initialize() and then bind all before handling the updateXYZ() message contents. (I.e. this involves either some synchronous patterns or careful task priority queue management.)

I've actually inferred a bit from the codebase itself, but I don't know if some patterns there are intentional and come with long-lasting guarantees, which is why I'm trying to find explicit architectural documentation instead.

Other Patterns

It's probably worth mentioning that I've already come up with (and tested) a few other patterns to provide some ordering-guarantees in my apps already, but I'm still trying to find good documentation on the R message-sending process and the JS message-handling process. Some of those other patterns are (very) briefly:

Input "Ready" Beacons

By managing some client-side state in your inputs, you can use Shiny.setInputValue() to send a special "ready" message to a different input slot on the R side. How do you know when your input is ready? Listen for the shiny:bound event (and possibly the shiny:connected event, if Shiny.shinyapp.isConnected() is currently false). The input on the R side can have a name like input$myInput__ready and the contents of that ready beacon message can be the client-side Date.now(), roughly guaranteeing a new "ready" signal each time the input is bound.

Overload the Input Value

Similar to the above, one can send an initial "ready" input value ( by overloading the binding's getValue() method) that indicates the element is bound but has not yet handled its first update via receiveMessage(). Observers on the R side would then need to differentiate between the "ready" value and a 'standard' input value. This has the advantage of guaranteeing boundness/connectedness (since it's using getValue()) without registering new event listeners, but has the additional complexity R-side of needing to conditionally handle different input values. This is more-or-less equivalent to the observeEvent(input$myInput, ...) example above, but explicitly helps to differentiate between when the input is first ready vs has-received its first updateXYZ(), which is when it might be truly ready/useful in the app context. (Best used with shiny::registerInputHandler() to help with that conditional logic.)

1 Like

Please check this issue (and support the FR):

Ah, yeah that GitHub issue is highly related; I'll join/follow the conversation over there; thanks!

This topic was automatically closed 54 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.