Handling missing inputs with req(…)
When writing Shiny apps, it’s fairly common to have a reactive expression or output that can only proceed under certain conditions.
Perhaps we need to wait until the user chooses a value from a selectInput
or clicks an actionButton
, and if such conditions are not met, the output should not be shown.
Or if your app uses renderUI
to dynamically populate the app’s input controls, then for a few moments during app startup, the inputs you depend on might not even exist.
If you write your application without considering such conditions, you may find your outputs rendering with ugly and confusing error messages. Here’s an example:
# Bad example; doesn't consider whether input$datasetName is ""
library(shiny)
<- fluidPage(
ui selectInput("datasetName", "Dataset", c("", "pressure", "cars")),
plotOutput("plot"),
tableOutput("table")
)
<- function(input, output, session) {
server <- reactive({
dataset get(input$datasetName, "package:datasets", inherits = FALSE)
})
$plot <- renderPlot({
outputplot(dataset())
})
$table <- renderTable({
outputhead(dataset(), 10)
})
}
shinyApp(ui, server)
The value of input$datasetName
starts out as ""
, which causes the dataset
reactive expression to fail.
A primitive solution is to add precondition checks to all reactive expressions and outputs, and return NULL
if any conditions are not met. Most (though not all) outputs will clear themselves if they are asked to render NULL
.
See below as we add if (...) return(NULL)
to dataset
, output$plot
, and output$table
.
# Bad example; manual checking for "" and NULL values everywhere
library(shiny)
<- fluidPage(
ui selectInput("datasetName", "Dataset", c("", "pressure", "cars")),
plotOutput("plot"),
tableOutput("table")
)
<- function(input, output, session) {
server <- reactive({
dataset if (input$datasetName == "")
return(NULL)
get(input$datasetName, "package:datasets", inherits = FALSE)
})
$plot <- renderPlot({
outputif (is.null(dataset()))
return(NULL)
plot(dataset())
})
$table <- renderTable({
outputif (is.null(dataset()))
return(NULL)
head(dataset(), 10)
})
}
shinyApp(ui, server)
While this does work, it seems a shame that such a simple app needs three different manual checks and early returns. If any new reactive expressions or outputs are introduced that depend on dataset
, they’ll also need to remember to check for null and return early.
Fortunately, there’s a better way.
Introducing req
The req(...)
function was introduced in Shiny 0.13 to simplify dealing with missing inputs and other preconditions. req
is short for “require”, so req(x)
can be read as either “require x
to be available”.
You call req
with one or more arguments. req
will evaluate each argument one at a time, and if it encounters an argument that it considers to be “missing” or “false” (see below for exactly what this means), it will stop.
Here’s our previous example again, using req
this time:
library(shiny)
<- fluidPage(
ui selectInput("datasetName", "Dataset", c("", "pressure", "cars")),
plotOutput("plot"),
tableOutput("table")
)
<- function(input, output, session) {
server <- reactive({
dataset # Make sure requirements are met
req(input$datasetName)
get(input$datasetName, "package:datasets", inherits = FALSE)
})
$plot <- renderPlot({
outputplot(dataset())
})
$table <- renderTable({
outputhead(dataset(), 10)
})
}
shinyApp(ui, server)
As you can see, dataset
uses the req
function, and the outputs don’t do any checking. Unlike using return(NULL)
, when you use req
to check your preconditions, a failure not only stops the current calculation (the dataset
reactive expression, in this case) but also any callers on the call stack. In this case, if the user has not chosen a dataset, then output$plot
and output$table
both stop upon calling dataset()
.
This is because when req
detects a failure, it doesn’t simply return, but actually raises an error by calling stop()
.
You can think of req
as being like base::stopifnot
, with a couple of key differences:
First, the error raised by
req
is a special, “silent” error that Shiny knows shouldn’t actually be displayed to the user, nor printed to the console.Second, while
stopifnot
simply checks if its arguments areTRUE
,req
has a more complicated set of rules that determine what arguments trigger an error.
Truthy and falsy values
The terms “truthy” and “falsy” generally indicate whether a value, when coerced to a logical, is TRUE
or FALSE
. We use the term a little loosely here; our usage tries to match the intuitive notions of “Is this value missing or available?”, or “Has the user provided an answer?”, or in the case of action buttons, “Has the button been clicked?”.
For example, a textInput
that has not been filled out by the user has a value of ""
, so that is considered a falsy value.
To be precise, req considers a value truthy unless it is one of:
FALSE
NULL
""
- An empty atomic vector
- An atomic vector that contains only missing values
- A logical vector that contains all FALSE or missing values
- An object of class
"try-error"
(see?base::try
) - A value that represents an unclicked
actionButton
Note in particular that the value 0
is considered truthy, even though as.logical(0)
is FALSE.
If the built-in rules for truthiness do not match your requirements, you can always work around them. Since FALSE
is falsy, you can simply provide the results of your own checks to req, e.g.: req(input$a != 0)
.
See also: validate/need
Since req
causes outputs to stop silently, it’s not useful in situations where the user needs to be told what values are missing or what user actions need to be taken to proceed.
In that case, you need the more flexible validation feature, which provides a superset of req
’s features via the validate/need functions; req(x)
is mostly just shorthand for validate(need(x, message = FALSE))
.
The downside of validate
is that its API is more complicated and less intuitive than req
, so we recommend that you stick to req
whenever you can.