This guide shows three ways to go beyond the components shipped with muiMaterial, from the simplest to the most flexible:
- Reusable components in R: R functions that return muiMaterial components.
- Custom React components: JavaScript components built with the MUI modules bundled in muiMaterial.
- Custom Shiny inputs: JavaScript components that send a value to the Shiny server.
Reusable components in R
In React, a reusable component is a function or a
styled() component. In R, it is a function that returns
muiMaterial components. Arguments set the props, and ...
passes the rest:
# A reusable "stat card"
StatCard <- function(title, value, trend, ...) {
up <- trend >= 0
Card(
variant = "outlined",
...,
CardContent(
Typography(sx = list(color = "text.secondary", fontSize = 14), gutterBottom = TRUE, title),
Typography(variant = "h4", component = "div", value),
Chip(
size = "small",
color = if (up) "success" else "error",
label = sprintf("%+.0f%%", trend),
sx = list(mt = 1)
)
)
)
}
muiMaterialPage(
CssBaseline(),
Grid(
container = TRUE,
spacing = 2,
Grid(size = list(xs = 12, sm = 4), StatCard("Users", "14k", 25)),
Grid(size = list(xs = 12, sm = 4), StatCard("Conversions", "325", -12)),
Grid(size = list(xs = 12, sm = 4), StatCard("Event count", "200k", 5, sx = list(bgcolor = "grey.50")))
)
)Most styled() components of the MUI documentation
translate this way: the styles become an sx list (see The sx prop in R), and
variants become R arguments. To restyle all instances
of a component, use the theme instead (see Theming).
Custom React components
For behaviour that needs JavaScript (state, effects,
styled()), define a React component in a script and render
it with shiny.react::reactElement(). The script finds React
and the bundled MUI modules on the global jsmodule object,
and registers the component under its own module name:
CustomComponents <- tags$script(HTML(
"(function() {
const React = jsmodule['react'];
const { Button, styled } = jsmodule['@mui/material'];
const CustomComponents = jsmodule['CustomComponents'] ??= {};
// A styled() component, as in the MUI documentation
const GradientButton = styled(Button)({
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
});
// A component with its own state: counts its clicks
CustomComponents.ClickCounter = function ClickCounter({ label }) {
const [count, setCount] = React.useState(0);
return React.createElement(GradientButton, { onClick: () => setCount(count + 1) }, `${label}: ${count}`);
};
})();"
))
ClickCounter <- function(...) {
shiny.react::reactElement(
module = "CustomComponents",
name = "ClickCounter",
props = shiny.react::asProps(...),
deps = muiMaterialDependency()
)
}
muiMaterialPage(
CssBaseline(),
CustomComponents,
ClickCounter(label = "Clicks")
)-
muiMaterialDependency()makes sure the muiMaterial bundle (which fillsjsmodule) is loaded before the component renders. - The script must be on the page before the component, as above.
- Write the component with
React.createElement(): the script is not compiled, so JSX is not available.
Custom Shiny inputs
shiny.react provides two adapters that turn a React component into a
Shiny input. All the .shinyInput() wrappers of muiMaterial
are built with them:
-
InputAdapter(Component, (value, setValue, props) => props)maps the value of the input to the props of the component. CallsetValue()to send a new value toinput[[inputId]]. An optional third argument limits how often the value is sent, for example{ policy: debounce, delay: 250 }. -
ButtonAdapter(Component)sends a click counter, likeButton.shinyInput().
The example below builds a text field that upper-cases its input. Its
value can be changed from the server with
shiny.react::updateReactInput():
library(shiny)
library(muiMaterial)
CustomInputs <- tags$script(HTML(
"(function() {
const { InputAdapter, debounce } = jsmodule['@/shiny.react'];
const { TextField } = jsmodule['@mui/material'];
const CustomInputs = jsmodule['CustomInputs'] ??= {};
CustomInputs.UpperCaseTextField = InputAdapter(TextField, (value, setValue) => ({
value: (value ?? '').toUpperCase(),
onChange: (e) => setValue(e.target.value.toUpperCase()),
}), { policy: debounce, delay: 250 });
})();"
))
UpperCaseTextField.shinyInput <- function(inputId, ..., value = "") {
shiny.react::reactElement(
module = "CustomInputs",
name = "UpperCaseTextField",
props = shiny.react::asProps(inputId = inputId, ..., value = value),
deps = muiMaterialDependency()
)
}
updateUpperCaseTextField.shinyInput <- shiny.react::updateReactInput
ui <- muiMaterialPage(
CssBaseline(),
CustomInputs,
Box(
sx = list(p = 2),
UpperCaseTextField.shinyInput("code", label = "Code", value = "abc"),
Button.shinyInput("reset", "Reset"),
verbatimTextOutput("value")
)
)
server <- function(input, output, session) {
output$value <- renderText(input$code)
observeEvent(input$reset, updateUpperCaseTextField.shinyInput(inputId = "code", value = ""))
}
shinyApp(ui, server)Run the bundled examples with
muiMaterialExample("CustomComponentShinyInput") and
muiMaterialExample("CustomComponentShinyInputStyled") (a
styled() slider).
Available JavaScript modules
The muiMaterial bundle registers these modules on
window.jsmodule:
| Module | Content |
|---|---|
jsmodule['react'],
jsmodule['react-dom']
|
React, provided by shiny.react |
jsmodule['@/shiny.react'] |
InputAdapter, ButtonAdapter,
debounce, … |
jsmodule['@mui/material'] |
all Material UI components and styled,
createTheme, alpha, … |
jsmodule['@mui/lab'] |
the lab components (Timeline, Masonry, TabContext, …) |
jsmodule['@mui/system'],
jsmodule['@mui/utils']
|
MUI System and utilities |
jsmodule['@emotion/react'],
jsmodule['@emotion/styled'],
jsmodule['@emotion/cache']
|
the Emotion styling engine |
jsmodule['@/muiMaterial'] |
the muiMaterial wrappers (.shinyInput,
.triggerId, ThemeProvider, …) |
@mui/icons-material is not bundled; see Icons.
Compatibility
- React 18. muiMaterial is built and tested against React 18, which is provided at runtime by shiny.react. A warning is printed in the browser console if another React version is loaded.
-
Other shiny.react packages. Packages built on
shiny.react (such as muiDataGrid,
shiny.fluent or shiny.blueprint) share the same
jsmoduleobject. The MUI modules above are shared on purpose, so that aThemeProvider()of muiMaterial also styles the components of companion packages. If another package registers a different copy of these modules, muiMaterial prints a warning in the browser console.
