req() inside case_when

I am obviously still not fully understanding req() despite reading the documentation, reviewing examples, and mucking about with it. I think it is pretty obvious in the following reprex what I am trying to do, choose a type of submission form then only allow submission of that form if all the inputs are complete, but it only works as desired if both types have been "touched". I was under the impression that req() only affected the block of code it is placed in, in this case the case_when functions, but the two req()'s are obviously affecting each other somehow. Please help me unscramble this in my brain!

I edited the code to a simpler example.

On start up the button is not shown, even when the criteria are satisfied:
image

If the Admin button is then selected and the criteria satisfied, the button is displayed:
image

If we then click back to User and enter both inputs, the button appears this time:
image

library(shiny)

ui <- fluidPage(
    br(),
    radioButtons("person_type", "Type",
                 choices = c("User" = "user", 
                             "Admin" = "admin"), inline = T),
    br(),
    uiOutput("person_info_ui"),
    br(),
    uiOutput("add_button"),
    br(),br()
)
    
server <- function(input, output, session) {
    output$person_info_ui <- renderUI({
        case_when(input$person_type == "user" ~ tagList(
            textInput("name","Name"),
            selectInput("group","Group",
                        choices = c("",'Red','Blue'),
                        selectize=F)),
            input$person_type == "admin" ~ tagList(
                textInput("name","Name"),
                textInput("id_num","ID Number"))
        )
    })
    
    output$add_button <- renderUI({
        case_when(input$person_type == "user" ~ {
            req(input$name, input$group)
            tagList(
                actionButton("add_user_db_button","Add User to Database"))
        },
        input$person_type == "admin" ~ {
            req(input$name, input$id_num)
            tagList(
                actionButton("add_admin_db_button","Add Admin to Database"))
        })
    })
    
    observeEvent(input$add_user_db_button, {
        # ADD TO DATABASE
        removeModal()
    })
    
    observeEvent(input$add_admin_db_button, {
        # ADD TO DATABASE
        removeModal()
    }) 
}
    
shinyApp(ui, server)

req()'s send silent errors; error are going to halt at the function call level, which is not quite the same thing as the nearest curly brackets. here is a simple illustration of that.

myfunc <- function() {
  {
  stop("bad")
}
cat("ok")
}

myfunc()

Here is one way you might achieve your desired behaviour
a) common conditions are fine candidates for guarding with a req() clause
b) individual requirements that must be met are just another set of logical conditions; shiny::isTruthy essentially uses the same evaluation as req() but rather than silent error raising, gives a checkable TRUE/FALSE result.

  output$add_button <- renderUI({
    req(input$name)
    case_when(input$person_type == "user" && isTruthy(input$group )~ {
      tagList(
        actionButton("add_user_db_button","Add User to Database"))
    },
    input$person_type == "admin"  && isTruthy(input$id_num )~ {
      tagList(
        actionButton("add_admin_db_button","Add Admin to Database"))
    })
  })
1 Like

@nirgrahamuk has a great answer! I'd like to add a little more context and give another solution based on a function I've come to appreciate: switch().

One issue with your use of dplyr's case_when() is that, like if_else() and base R's ifelse() functions, is that all of these functions are vectorized on all arguments. In other words, the condition, the value when true and the value when false are all evaluated completely. If you want code to run only when the condition is true or false, then you need to use a structure like

if (condition) {
  value_when_true 
} else {
  value_when_false
}

When the condition is a character value, though, you can use switch() instead of if ... else. This is really handy in situations like yours:

type <- "admin"
switch(
  type,
  user = "value when type is user",
  admin = "value when type is admin",
  "a default value when type is something else"
)
#> [1] "value when type is admin"

Switch also helpfully only evaluates the code that matches the input value, so you can replace your renderUI() logic with this:

  output$add_button <- renderUI({
    switch(
      input$person_type,
      user = {
        req(input$name, input$group)
        actionButton("add_user_db_button", "Add User to Database")
      },
      admin = {
        req(input$name, input$id_num)
        actionButton("add_admin_db_button", "Add Admin to Database")
      }
    )
  })
1 Like

Understood. Thanks so much for the detailed response, @nirgrahamuk !

Thanks, @grrrck . This really helps my understanding. Especially:

I had stumbled upon switch() and thought it might offer a solution, but hadn't really dug in. Great to have a concrete example to go off of. Thank you so much!

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.