6 Error Messages

The latest example app we built can be accessed below, or at example2.

The app works well enough, but what happens if we deselect all of the options in the input? Try that now.

Depending on where we are running the app, we will receive different error messages. If we uncheck all three options while running the app through the Shiny server, we will receive this message:

Error: An error has occurred. Check your logs or contact the app author for clarification.

Or, if we are running the app locally, we will receive this message:

Error: 0 (non-NA) cases

Either way, these are not messages we would want to return to users. If a user sees the first message, they will not check the logs because they cannot, and they are probably more likely to close their window than to send you an email. If a user sees the second message, this is even more of a problem. The whole reason we are using Shiny is to allow non-R-users to run R sessions without knowing R, but now we have returned an R error message!

Shiny allows us to take control of the error messages we return to users. The messages above are ineffective because they either do not provide helpful suggestions or because they are not readily interpretable. The messages we return should be the opposite; we need to tell users how to fix the problem in a way that they can understand.

To display custom error messages, follow these steps:

  1. Identify the source of the error.
  2. Craft a logical test that, if passed, will allow the app to run properly.
  3. Write an error message that tells the user how to fix the error.
  4. Rewrite a reactive expression with the logical test and error message.

Previously, our reactive expression followed this format, where OBJECT was dat and EXPRESSION was mtcars |> filter(cyl %in% input$cyls).

OBJECT <-
  reactive({
    EXPRESSION
  })

What we need to do is rewrite our reactive expression by creating a logical test (CONDITION) that if failed, will return MESSAGE_IF_FALSE, and if passed, will return our original expression, now called EXPRESSION_IF_TRUE.

OBJECT <-
  reactive({
    validate(need(try(CONDITION),
                  MESSAGE_IF_FALSE)
             )
    EXPRESSION_IF_TRUE
  })

To adapt the code, we can follow the four steps outlined above.

  1. Error source: The app filters the mtcars dataset so that only cases with certain levels of cyl remain. The app fails when none are checked because it tries to plot and fit a model to an empty dataset.
  2. Logical test: A simple test would be to check whether at least one of the options in input$cyls is checked, if length(input$cyls) > 0.
  3. Helpful error message: To fix the error, users need to check at least one of the choices for cylinders, so we should say, “Please make at least one selection for cylinders.”
  4. Rewrite reactive expression: See below.
dat <-
  reactive ({
    validate(need(try(length(input$cyls) > 0),
                  "Please make at least one selection for cylinders.")
             )
    mtcars |> filter(cyl %in% input$cyls)
  })

The actual filtering of the dataset does not result in an error (try filter(mtcars, cyl %in% c())), but rather the subsequent plotting and model fitting. We have the option of delaying the validate() test to the following reactive expression where we fit the model, as follows:

coefs <-
  reactive({
    validate(need(try(length(input$cyls) > 0),
                  "Please make at least one selection for cylinders.")
             )
    lm(mpg ~ wt, data = dat()) |> coef()
  })

Our validate() code just needs to be within a reactive expression and before an error would have resulted. The lm() call results in an error when passed an empty dataset (try lm(mpg ~ wt, filter(mtcars, cyl %in% c()))).

The logical test could also have been nrow(dat) > 0, since technically our error arises from an empty dataset rather than from no values in the input. We could have gone one step further, and depending on how many model parameters we wanted to estimate or on what we considered a minimum number of observations needed for a meaningful result, we could have used a test for another number of minimum rows. Our approach earlier was simply avoiding an R error message, but perhaps we, as the relative experts of the subject matter and statistics, have more to say. We might feel that it is irresponsible to allow users to so reduce a dataset that relationships among variables may be distorted, so we can choose to display no output if the input selections result in too small of a dataset (however we consider it).

In other words, contrary to the title of this section, we can use validate() for more than just error messages, such as other recommendations. However, it would be better to restrict the capabilities of the app so that users cannot make selections we advise against. We are making apps for data communication, not puzzle games.

Back to our example, we can add the validate() code to see how a more informative message is displayed if no choices are selected in the input of example3:

Now, the user can get a clear idea of what they should do to correct the problem

6.1 Exercises

Check Your Understanding:

Copy and paste the following code into a new R script and save it. Click “Run App”. The app is a modified version of our earlier example1, where, for some reason, we wanted to include “hp” as an option but do not want users to select it. Modify the app in these ways:

  1. Change the text in message that is shown when a user selects “hp” to something else.

  2. Change the test so that it returns a message when a user selects “wt” instead of “hp”.

library(shiny)

ui <- 
  fluidPage(
    selectInput(
      inputId = "xvar",
      label = "Pick a variable for the x-axis:",
      choices = c("wt", "hp"),
      selected = "wt"
    ),
    
    plotOutput(outputId = "myPlot")
  )

server <- 
  function(input, output) {
    
    dat <- 
      reactive({
        validate(need(try(input$xvar != "hp"), 
                      "I don't recommend choosing hp."))
        mtcars
      })
    
    output$myPlot <- 
      renderPlot({
        plot(dat()[, input$xvar], mtcars$mpg)
      })
  }

shinyApp(ui = ui, server = server)

Create Your Own App:

Add a custom error message to your app. If your app does not return any errors, * Try every combination of values in the input. Do your best to break your app. Your users will. * Add a new render*() function that will return an error if certain values are taken. * Return an “error message” that makes a recommendation under certain conditions.