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)

ui <- fluidPage(
  selectInput("datasetName", "Dataset", c("", "pressure", "cars")),
  plotOutput("plot"),
  tableOutput("table")
)

server <- function(input, output, session) {
  dataset <- reactive({
    get(input$datasetName, "package:datasets", inherits = FALSE)
  })

  output$plot <- renderPlot({
    plot(dataset())
  })

  output$table <- renderTable({
    head(dataset(), 10)
  })
}

shinyApp(ui, server)

The value of input$datasetName starts out as "", which causes the dataset reactive expression to fail.

Example with errors

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)

ui <- fluidPage(
  selectInput("datasetName", "Dataset", c("", "pressure", "cars")),
  plotOutput("plot"),
  tableOutput("table")
)

server <- function(input, output, session) {
  dataset <- reactive({
    if (input$datasetName == "")
      return(NULL)
    get(input$datasetName, "package:datasets", inherits = FALSE)
  })

  output$plot <- renderPlot({
    if (is.null(dataset()))
      return(NULL)
    plot(dataset())
  })

  output$table <- renderTable({
    if (is.null(dataset()))
      return(NULL)
    head(dataset(), 10)
  })
}

shinyApp(ui, server)

Example without errors

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)

ui <- fluidPage(
  selectInput("datasetName", "Dataset", c("", "pressure", "cars")),
  plotOutput("plot"),
  tableOutput("table")
)

server <- function(input, output, session) {
  dataset <- reactive({
    # Make sure requirements are met
    req(input$datasetName)

    get(input$datasetName, "package:datasets", inherits = FALSE)
  })

  output$plot <- renderPlot({
    plot(dataset())
  })

  output$table <- renderTable({
    head(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 are TRUE, 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.



If you have questions about this article or would like to discuss ideas presented here, please post on RStudio Community. Our developers monitor these forums and answer questions periodically. See help for more help with all things Shiny.


Start
Build
Improve