This guide explains how muiMaterial components talk to a Shiny
server: the .shinyInput() wrappers, the
update*.shinyInput() functions, the reserved props, and the
lower-level helpers triggerEvent(),
setInput(), JS(), renderReact()
and reactOutput().
.shinyInput() wrappers
A plain component such as Slider() renders in the
browser only. Its .shinyInput() variant,
Slider.shinyInput(inputId, ...), also sends its value to
the server as input[[inputId]]:
library(shiny)
library(muiMaterial)
ui <- muiMaterialPage(
CssBaseline(),
Box(
sx = list(p = 2, width = 300),
Slider.shinyInput("size", value = 30, min = 0, max = 100, valueLabelDisplay = "auto"),
verbatimTextOutput("value")
)
)
server <- function(input, output, session) {
output$value <- renderPrint(input$size)
}
shinyApp(ui, server)- The first argument is the
inputId; every other argument is a prop of the MUI component, exactly as with the plain component. -
valuesets the initial value.
There are two kinds of .shinyInput() wrappers.
Value inputs report their current value:
| Function | input[[inputId]] |
|---|---|
Autocomplete.shinyInput() |
the selected option, or a vector with
multiple = TRUE
|
BottomNavigation.shinyInput() |
the value of the selected action |
Checkbox.shinyInput(),
Switch.shinyInput(), Radio.shinyInput(),
FormControlLabel.shinyInput()
|
TRUE or FALSE
|
TextField.shinyInput(),
Input.shinyInput(),
OutlinedInput.shinyInput(),
FilledInput.shinyInput()
|
the text (sent 250ms after the last keystroke) |
Select.shinyInput(),
NativeSelect.shinyInput()
|
the selected value, or a vector with
multiple = TRUE
|
RadioGroup.shinyInput() |
the value of the selected radio |
Slider.shinyInput() |
a number, or a vector of two for a range (sent 250ms after the last move) |
Rating.shinyInput() |
a number |
Pagination.shinyInput() |
the page number (starting at 1) |
Tabs.shinyInput(),
TabList.shinyInput()
|
the value of the selected tab |
ToggleButtonGroup.shinyInput() |
the selected value, or a vector without
exclusive = TRUE
|
Action inputs report a click counter, like
shiny::actionButton(): Button.shinyInput(),
IconButton.shinyInput(), Fab.shinyInput(),
LoadingButton.shinyInput(),
ListItemButton.shinyInput(),
MenuItem.shinyInput(),
StepButton.shinyInput(),
ToggleButton.shinyInput(). Use them with
observeEvent().
The overlays Dialog.shinyInput(),
Drawer.shinyInput(), Menu.shinyInput(),
Modal.shinyInput() and Snackbar.shinyInput()
are also action inputs: they count clicks inside the surface. Their
open state is set from the server; see Dialog and Snackbar. For overlays
opened by a button, .triggerId
is simpler.
The bundled showcase app lists all of them:
muiMaterialExample("showcase")Updating inputs from the server
Each .shinyInput() has an
update*.shinyInput() function. It changes any
prop of the component, not only its value:
ui <- muiMaterialPage(
CssBaseline(),
Stack(
spacing = 2,
sx = list(p = 2, width = 300),
TextField.shinyInput("name", label = "Name", value = ""),
Button.shinyInput("reset", "Reset", variant = "outlined"),
Button.shinyInput("submit", "Submit", variant = "contained", disabled = TRUE)
)
)
server <- function(input, output, session) {
# Enable the submit button once a name is typed
observe({
updateButton.shinyInput(inputId = "submit", disabled = !nzchar(input$name))
})
# Clear the field and change its label
observeEvent(input$reset, {
updateTextField.shinyInput(inputId = "name", value = "", label = "Name (cleared)")
})
}
shinyApp(ui, server)session is the first argument of the update functions
and defaults to the current session, so inputId must be
named.
Reserved props
A .shinyInput() wrapper owns the props that carry its
value:
- value inputs own
value(orchecked/page) andonChange, - action inputs own
onClick.
Passing one of them through ... replaces the wrapper’s
own handler, and input[[inputId]] stops updating.
muiMaterial warns when this happens:
Switch.shinyInput("dark", checked = TRUE)
#> Warning: Switch.shinyInput(): `checked` is a reserved prop -- a caller-supplied
#> value overrides the .shinyInput wiring and `input[[inputId]]` may stop updating. ...Set the initial state with the value argument instead:
Switch.shinyInput("dark", value = TRUE). For a custom
browser-to-server signal on a component, use a plain component with
onClick = triggerEvent("name") (see below).
Overriding the wiring on purpose is a legitimate advanced pattern,
for example when the server is the single source of truth and pushes
checked down with renderReact(), as in the Transfer List
example. Silence the warning with:
options(muiMaterial.warnReservedProps = FALSE)Events without an input: triggerEvent() and
setInput()
Any callback prop of any component (onClick,
onChange, onClose, …) can send a value to the
server, without a .shinyInput() wrapper:
-
triggerEvent("name")setsinput$nameeach time the callback runs. Use it withobserveEvent(input$name, ...). -
setInput("name", accessor)setsinput$nameto one of the arguments of the callback:setInput("name")is the first argument,setInput("name", 1)the second one, andsetInput("name", "[0].target.value")a JavaScript accessor.
ui <- muiMaterialPage(
CssBaseline(),
Box(
sx = list(p = 2),
Chip(label = "Delete me", onDelete = triggerEvent("chip_deleted")),
Accordion(
onChange = setInput("details_expanded", 1), # onChange(event, expanded)
AccordionSummary("Details"),
AccordionDetails("Content")
),
verbatimTextOutput("state")
)
)
server <- function(input, output, session) {
output$state <- renderPrint(list(deleted = input$chip_deleted, expanded = input$details_expanded))
}
shinyApp(ui, server)JavaScript callbacks: JS()
Props that expect a JavaScript function (callbacks, render functions,
sx functions of the theme) are written as strings marked
with JS(). The function runs in the browser:
Button(onClick = JS("() => alert('Hello from the browser')"), "Say hello")
# Send a custom value with Shiny's JavaScript API
MenuItem(onClick = JS("() => Shiny.setInputValue('action', 'edit', {priority: 'event'})"), "Edit")
# sx as a function of the theme
Box(sx = JS("(theme) => ({ color: theme.palette.primary.main })"), "Themed text"){priority: 'event'} makes Shiny send the value even when
it did not change, so observeEvent() runs on every click.
When a component prop expects a component (not an element), pass it by
reference, for example
slots = list(transition = JS("jsmodule['@mui/material'].Fade")).
Server-side rendering: renderReact() and
reactOutput()
Render muiMaterial components from the server with either:
-
shiny::renderUI()andshiny::uiOutput(), or -
renderReact()andreactOutput(), re-exported from shiny.react.
renderReact() updates the React elements already on the
page instead of replacing them. Transitions run, and the state of
components that did not change is kept. Prefer it for components that
change often, such as progress bars or transitions:
ui <- muiMaterialPage(
CssBaseline(),
Box(sx = list(p = 2, width = 300), Slider.shinyInput("value", value = 40), reactOutput("progress"))
)
server <- function(input, output, session) {
output$progress <- renderReact({
LinearProgress(variant = "determinate", value = input$value)
})
}
shinyApp(ui, server)Bookmarking
Shiny’s bookmarking
saves input values in the URL, but restores them only for
Shiny’s own input bindings. Restore .shinyInput() values in
onRestore() with their update function:
ui <- function(request) {
muiMaterialPage(
CssBaseline(),
Box(
sx = list(p = 2),
TextField.shinyInput("txt", label = "Enter text", value = "initial"),
verbatimTextOutput("show_txt")
)
)
}
server <- function(input, output, session) {
output$show_txt <- renderPrint(input$txt)
onRestore(function(state) {
updateTextField.shinyInput(session, "txt", value = state$input$txt)
})
# Update the URL on every change
observe({
reactiveValuesToList(input)
session$doBookmark()
})
onBookmarked(function(url) updateQueryString(url))
}
shinyApp(ui, server, enableBookmarking = "url")shiny::bookmarkButton() needs Bootstrap, which
muiMaterialPage() removes. Updating the URL automatically,
as above, avoids the button. Run this example with
muiMaterialExample("bookmarking").
For state kept in the URL on the client side (no server), see Using a router.
