2

How one should check whether or not the validate(need()) conditions are fulfilled in a reactive expression?

Trying to get access to a reactive expression when this one does not fulfill the conditions lead to a silent error and stops the function/observer whatever stored the initial call. While this makes perfect sense from an UI perspective, how to handle it from a function perspective and prevent a premature stop?

Below an example (based on the genuine Shiny Validate tutorial) which replicates this behavior: until you select a value in the selectInput(), you will not see the console message "This message will only be printed when data() validate/need are fulfilled." when clicking the button because the observeEvent() is stopped. In the meantime, you may still be eager to do something, like writing an empty file or triggering something at this point, etc.

require(shiny)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput("data", label = "Data set",
                  choices = c("", "mtcars", "faithful", "iris"))
    ),
    
    mainPanel(
      actionButton(inputId = "actbtn", label = "Print in console reactive value")
    )
  )
)

server <- function(input, output) {
  
  data <- reactive({
    validate(
      need(input$data != "", "Please select a data set"),
      need(input$data %in% c("mtcars", "faithful", "iris"),
           "Unrecognized data set"),
      need(input$data, "Input is an empty string"),
      need(!is.null(input$data),
           "Input is not an empty string, it is NULL")
    )
    get(input$data, 'package:datasets')
  })
  
  observeEvent(input$actbtn, {
    print(data())
    print("This message will only be printed when data() validate/need are fulfilled.")
  })
}

shinyApp(ui, server)

I tried to check the "state" of the reactive expression prior to actually calling it, without success for now. I can of course adapt my app (for instance by doing a validation-like by myself or just re-performing the conditions prior to calling the reactive expression) but I am quite confident that there is a smarter way to handle this context.

yeahman269
  • 705
  • 7
  • 16

1 Answers1

1

When using req or validate/need, the chain of reactivity is disrupted if not completely valid. This means that anything that depends on the reactive block will also not fire (due to the data block). I don't know of a way to "peek" into the reactive block's validation, since that would involve evaluating the expression and catching the shiny-specific signals.

Typically, it is sufficient in a shiny app that the actbtn observer will not fire due to data not validating. However, if you need to know in one expression whether another is fire-able, you can set up a third reactive block that always returns a known element (i.e., no validate in it).

library(shiny) # don't use require without checking its return value
               # https://stackoverflow.com/a/51263513/3358272

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput("data", label = "Data set",
                  choices = c("", "mtcars", "faithful", "iris"))
    ),
    
    mainPanel(
      actionButton(inputId = "actbtn", label = "Print in console reactive value")
    )
  )
)

server <- function(input, output) {
  
  isgood <- reactive({
    if (is.null(input$data) || !nzchar(input$data)) {
      "Please select a data set"
    } else if (!input$data %in% c("mtcars", "faithful", "iris")) {
      "Unrecognized data set"
    } else character(0)
  })

  data <- reactive({
    validate(need(!length(isgood()), isgood()))
    get(input$data, 'package:datasets')
  })
  
  observeEvent(input$actbtn, {
    validate(need(!length(isgood()), isgood()))
    print("This message will only be printed when data() validate/need are fulfilled.")
  })
}

shinyApp(ui, server)

An alternative is to disable the button (cannot be clicked) until data() is valid. This can be done with shinyjs, which also requires a change to the ui component.

library(shiny)
library(shinyjs)

ui <- fluidPage(
  shinyjs::useShinyjs(),                    # <-- add this
  sidebarLayout(
    sidebarPanel(
      selectInput("data", label = "Data set",
                  choices = c("", "mtcars", "faithful", "iris"))
    ),

    mainPanel(
      actionButton(inputId = "actbtn", label = "Print in console reactive value")
    )
  )
)

server <- function(input, output) {

  observeEvent(TRUE, {
    message("quux")
    shinyjs::disable("actbtn")
  }, once = TRUE)

  data <- reactive({
    validate(
      need(input$data != "", "Please select a data set"),
      need(input$data %in% c("mtcars", "faithful", "iris"),
           "Unrecognized data set"),
      need(input$data, "Input is an empty string"),
      need(!is.null(input$data),
           "Input is not an empty string, it is NULL")
    )
    get(input$data, 'package:datasets')
  })

  observeEvent(data(), {
    shinyjs::toggleState("actbtn", condition = !is.null(data()))
  })

  observeEvent(input$actbtn, {
    print("This message will only be printed when data() validate/need are fulfilled.")
  })
}

shinyApp(ui, server)

Due to the choices you have available in your UI, the toggleState observer will only ever toggle the disabled state once, after that it should always be valid. Because of that, there are ways to possibly reduce it. For instance, once could add once=TRUE as in the first observeEvent I added, in which case once the observer fires once, it is removed and never fires again. In your real-world use, if you believe that data() might toggle back to an unusable state (e.g., 0 rows), then you would likely want to keep the default of once=FALSE and add more logic on nrow(data()) to your data validation.

r2evans
  • 141,215
  • 6
  • 77
  • 149
  • Thanks for the reply. I have indeed some workarounds but really wanted to know if there was a way to "check" the state of the reactive expression. You seem to say that there are none. That's a shame. Do you think RStudio will provide something for it in the future? – yeahman269 Mar 24 '21 at 12:13
  • You can ask. I haven't seen a large demand for it, and since I work on shiny very often and haven't seen a need for it (where something like the above wasn't more general anyway), I don't see a lot of impetus to do it. But that's just me, there are plenty of others out there. Perhaps one of shiny's authors will know an easy way to do it on-the-cheap. – r2evans Mar 24 '21 at 13:13