Registers widget as a shiny::renderUI() output named
paste0(id, suffix), the way shinyreact::page_react() apps host a
bslib or shiny widget the client displays with <ShinyOutput id="<id><suffix>" /> (or the matching kit component, HostedInput,
from the bslibreact npm package) instead of rebuilding the widget's
markup in React. See docs/decisions/0003-hosted-widgets.md in the
repository for the reasoning behind hosting widgets this way instead of
driving them from React state.
Arguments
- output
The
outputobject from a Shiny server function.- id
A short, stable identifier for this hosted widget. This is not itself a DOM id: it is combined with
suffixto make the output name, soidonly has to be distinct from any otherhost_input()call'sidin the same app, and independent of whatever input id the widget itself declares.- widget
An expression that builds the widget, evaluated lazily inside
shiny::renderUI()each time it is invalidated (so it can read reactive values, the same as anyrenderUI()block). Typically a single unevaluated widget call, for exampleshiny::sliderInput("bins", "Bins", 1, 50, 30).- suffix
Appended to
idto make the output name. With the default,"_ui",host_input(output, "bins", ...)registersoutput$bins_ui, and the client shows it with<SliderInput id="bins_ui" />or any otherHostedInput: the client always gets the full output name, suffix included.
Why the suffix matters
A hosted widget contributes two elements to the page, not one: the
output wrapper Shiny creates for output[[paste0(id, suffix)]] (a
<div id="foo_ui" class="shiny-html-output">, in the default case), and
the widget's own element, with the widget's own input id, nested inside
it (a <div id="foo" class="form-group shiny-input-container">, for a
shiny::sliderInput("foo", ...)). These are two different ids on
purpose: the wrapper is what the client's HostedInput component
addresses to swap in new markup, and the widget's own id is what Shiny's
input binding addresses to read input$foo. If the two ever match (a
widget whose own id is "foo_ui", or suffix = ""), the page holds two
elements under one id; document.getElementById(), Shiny's own
$("#foo") lookups, and every downstream consumer of that id become
ambiguous, silently addressing whichever one the browser happens to
return first. check_no_id_collision() (an internal helper) catches
this at render time with a message naming the id involved, instead of
leaving it to surface later as an input or output that mysteriously
never updates.
The output keeps rendering while it is hidden (suspendWhenHidden = FALSE), so a widget inside a closed panel or an inactive tab exists
before it is first shown, as it would in the server-rendered app.
Examples
if (FALSE) { # \dontrun{
server <- function(input, output, session) {
host_input(output, "bins", shiny::sliderInput("bins", "Bins", 1, 50, 30))
# The client displays this with <ShinyOutput id="bins_ui" />, and reads
# the slider's own value the usual way, from input$bins.
}
} # }