shinyvalidate - using a reactive expression witin add_rule()

I am trying to implement user feedback for an app I'm wokring on using the shinyvalidate package. Some of the feedback I want to communicate to the user is whether a value they have selected is within a specific range or not. The range of interest depends on another input they have made (I have included a simplified repex of the code).

Thus, whether the precondition of interest is met depends on a reactive value. I first define a function that checks whether a provided value is within a specified range. Then I call this function within add_rule() using a reactive as an argument to the function. This results in an error, which states that I cannot access the reactive using add_rule. This is supposedly the case because the InputValidator object is not a reactive consumer.

Error message:

Warning: Error in : Can't access reactive value 'num1' outside of reactive consumer.
i Do you need to wrap inside reactive() or observer()?
  55: <Anonymous>
Error : Can't access reactive value 'num1' outside of reactive consumer.
i Do you need to wrap inside reactive() or observer()?

However, if I use an unnamed function within add_rule(), I can specify a range which depends on a reactive and I no longer get the error message. The unnamed and named functions I use are identical and I don't understand why I get error message using a named function but I do not get the error message when using the named function.

Here is my code using the named function:

library(shiny)
library(shinyvalidate)
checkRange <- function(value, value2){
  if(value < -2 * value2 || value > 2 * value2 ){
    paste0("Please specify a number that is within the range: ", -2 * value2, ":", 2 * value2, " - Tank you!")
  }
}

ui <- fluidPage(
  fluidRow(
           numericInput("num1",
                        "Please specify your first number",
                        value = NULL),
           numericInput("num2",
                        "Please specify a different number",
                        value = NULL),
           verbatimTextOutput("selectedNums")
  
)
)

server <- function(input, output, session){
  iv <- InputValidator$new()
  iv$add_rule("num1", sv_required())
  iv$add_rule("num2", sv_required())
  iv$add_rule("num2", checkRange, value2 = input$num1)
  iv$enable()
  output$selectedNums <- renderPrint({
    req(iv$is_valid())
    paste0("The first number = ", input$num1, " and the second number = ", input$num2)
  })
}

app <- shinyApp(ui, server)

runApp(app)

And here is the code using an anonymous function (UI and server code are largely identical with the exception to one call to iv$add_rule()):

library(shiny)
library(shinyvalidate)
ui <- fluidPage(
  fluidRow(
           numericInput("num1",
                        "Please specify your first number",
                        value = NULL),
           numericInput("num2",
                        "Please specify a different number",
                        value = NULL),
           verbatimTextOutput("selectedNums")
  
)
)

server <- function(input, output, session){
  iv <- InputValidator$new()
  iv$add_rule("num1", sv_required())
  iv$add_rule("num2", sv_required())
  iv$add_rule("num2", function(value){
    if(value < - 2 * input$num1 || value > 2 * input$num2){
      paste0("Please specify a number that is within the range: ", -2 * input$num1, ":", 2 * input$num1, " - Tank you!")
    }
  })
  iv$enable()
  output$selectedNums <- renderPrint({
    req(iv$is_valid())
    paste0("The first number = ", input$num1, " and the second number = ", input$num2)
  })
}

app <- shinyApp(ui, server)

runApp(app)

I would prefer to use the named function since I would like to reuse the code multiple times. Could anyone help me out as to why I get an error message with the named function but not with the unnamed one?

You pass value2 = input$num1 outside of a reactive, that is the issue. Compare the two version below (see commented line vs the one below that):

library(shiny)
library(shinyvalidate)

checkRange1 <- function(value, value2){
  if(value < -2 * value2 || value > 2 * value2 ){
    paste0("Please specify a number that is within the range: ", -2 * value2, ":", 2 * value2, " - Tank you!")
  }
}

checkRange2 <- function(value){
    if(value < - 2 * input$num1 || value > 2 * input$num2){
      paste0("Please specify a number that is within the range: ", -2 * input$num1, ":", 2 * input$num1, " - Tank you!")
    }
}

ui <- fluidPage(
  fluidRow(
           numericInput("num1",
                        "Please specify your first number",
                        value = NULL),
           numericInput("num2",
                        "Please specify a different number",
                        value = NULL),
           verbatimTextOutput("selectedNums")
  
)
)

server <- function(input, output, session){
  iv <- InputValidator$new()
  iv$add_rule("num1", sv_required())
  iv$add_rule("num2", sv_required())
  #iv$add_rule("num2", checkRange1, value2 = input$num1)
  iv$add_rule("num2", checkRange2)
  iv$enable()
  output$selectedNums <- renderPrint({
    req(iv$is_valid())
    paste0("The first number = ", input$num1, " and the second number = ", input$num2)
  })
}

app <- shinyApp(ui, server)

runApp(app)

Thank you very much for your suggestion! Unfortunately I get the following error message when providing values for the two numeric inputs in the app:

Warning: Error in <Anonymous>: object 'input' not found
  64: <Anonymous> [#2]
  62: rule$rule
  48: <Anonymous>
  47: mapply
  46: self$`_validate_impl`
  45: self$validate
  44: <observer>
   1: runApp 

Also, I do not quite understand why using an unnamed function within add_rule() or the named function suggested by you would provide a reactive context. I think I am having a major misunderstanding about reactivity here and I would appreciate any resource that might explain why one version of the code works while the other does not...

Thank you very much that you took the time to concern yourself with my problem!

I see. The tricky part is how to pass the input, which looks like is not accessible that way I defined it. The following works: you can pass the input as an argument. When calling the fucntion input is a list, when inside the server function it will be the reactive input object.

library(shiny)
library(shinyvalidate)

checkRange <- function(value, input){
    if(value < - 2 * input$num1 || value > 2 * input$num2){
      paste0("Please specify a number that is within the range: ", -2 * input$num1, ":", 2 * input$num1, " - Tank you!")
    }
}

ui <- fluidPage(
  fluidRow(
           numericInput("num1",
                        "Please specify your first number",
                        value = NULL),
           numericInput("num2",
                        "Please specify a different number",
                        value = NULL),
           verbatimTextOutput("selectedNums")
  )
)

server <- function(input, output, session){
  iv <- InputValidator$new()
  iv$add_rule("num1", sv_required())
  iv$add_rule("num2", sv_required())
  iv$add_rule("num2", checkRange, input)
  iv$enable()
  output$selectedNums <- renderPrint({
    req(iv$is_valid())
    paste0("The first number = ", input$num1, " and the second number = ", input$num2)
  })
}

app <- shinyApp(ui, server)

runApp(app)

This time I tested the app with inputs and it is working :slight_smile:

Thanks again for looking into it - fantastic that it works now! However, I still do not understand why the code works and mine did not - i.e. I do not understand why one function has issues handling a reactive input and the other does not - could you maybe point me to some resource that might explain it, because nothing I have read so far helped me understand the solution. Thank you again!

I think it comes down to the parent environment where the function is defined. When you define it in Globalenv, there is no reference to input. When the function is created withing the shinyvalidate R6 objects method function, input is already in scope because it is passed to the server function. So the fix works because you pass input explicitly.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.