Search code examples
rshinyshinyvalidate

How to use R shinyvalidate to test if two inputs (passwords) are equal?


I'm trying to use the R package shinyvalidate to do something very simple: check if a password field and a re-enter password field are identical.

The most intuitive way to me to try to do this was just to add a rule directly that the two password inputs must match:

library(shiny)
library(shinyvalidate)

# Define ui ----
ui <- fluidPage(
  uiOutput("registration_page")
)

# Define server ----
server <- server <- function(input, output) {
  output$registration_page <- renderUI({
    fluidPage(
      textInput("regis_username", "Username:"),
      passwordInput("regis_password", "Password:"),
      passwordInput("regis_password2", "Re-enter password:"),
      actionButton("submit_registration", "Submit Registration")
    )
  })
  
  #Registration input validator
  reg_iv <- InputValidator$new()
  reg_iv$add_rule("regis_password", sv_required())
  reg_iv$add_rule("regis_password2", sv_required())
  reg_iv$add_rule("regis_password2",
                  sv_equal(input$regis_password, message_fmt = "Must match password"))
  reg_iv$enable()
}

shinyApp(ui = ui, server = server)

However, this code fails with the error message Error in input$regis_password: Can't access reactive value 'regis_password' outside of reactive consumer. Do you need to wrap inside reactive() or observe()?

So then I wrap the rules in an observeEvent call, so they're reactive and update every time regis_password is edited.

library(shiny)
library(shinyvalidate)

# Define ui ----
ui <- fluidPage(
  uiOutput("registration_page")
)

# Define server ----
server <- server <- function(input, output) {
  output$registration_page <- renderUI({
    fluidPage(
      textInput("regis_username", "Username:"),
      passwordInput("regis_password", "Password:"),
      passwordInput("regis_password2", "Re-enter password:"),
      actionButton("submit_registration", "Submit Registration")
    )
  })
  
  #Registration input validator
  reg_iv <- InputValidator$new()
  reg_iv$add_rule("regis_password", sv_required())
  observeEvent(input$regis_password, {
    reg_iv$add_rule("regis_password2", sv_required())
    reg_iv$add_rule("regis_password2",
                    sv_equal(input$regis_password, message_fmt = "Must match password"))
  })
  reg_iv$enable()
}

shinyApp(ui = ui, server = server)

Now the Must match password validation failure is always showing, I think because every change to regis_password results in a new rule but the old match rules are still there, so there's no way to match them all?

So then I make a new validator just for the 2nd password:

library(shiny)
library(shinyvalidate)

# Define ui ----
ui <- fluidPage(
  uiOutput("registration_page")
)

# Define server ----
server <- server <- function(input, output) {
  output$registration_page <- renderUI({
    fluidPage(
      textInput("regis_username", "Username:"),
      passwordInput("regis_password", "Password:"),
      passwordInput("regis_password2", "Re-enter password:"),
      actionButton("submit_registration", "Submit Registration")
    )
  })
  
  #Registration input validator
  reg_iv <- InputValidator$new()
  reg_iv$add_rule("regis_password", sv_required())
  observeEvent(input$regis_password, {
    reg_password2_iv <- InputValidator$new()
    reg_password2_iv$add_rule("regis_password2", sv_required())
    reg_password2_iv$add_rule(
      "regis_password2",
      sv_equal(input$regis_password, message_fmt = "Must match password"))
    reg_password2_iv$enable()
  })
  reg_iv$enable()
  
  observeEvent(input$submit_registration, {
    req(reg_iv$is_valid())
    req(reg_password2_iv$is_valid())
  })
}

shinyApp(ui = ui, server = server)

This has the desired effect for the validator rules, but produces an error once we try to actually check whether the validator rules are met: the line req(reg_password2_iv$is_valid()) produces the error message Error in observe: object 'reg_password2_iv' not found with the trace below.

89: eval
88: eval
85: dotloop
84: req
83: observe [#33]
82: <observer:observeEvent(input$submit_registration)>
 3: runApp
 2: print.shiny.appobj
 1: <Anonymous>

So, what am I doing wrong? I've scoured the internet as best I can but I can't find a simple example where two input$ fields are compared against each other reactively with shinyvalidate


Solution

  • It seems to be that sv_equal() is not working with reactive values. However, instead it is possible to just pass a custom function to add_rule() which does the comparison job:

    reg_iv$add_rule("regis_password2", 
                    function(value, input){
                      if (value != input$regis_password) {
                        "Must match password"
                      } 
                    }, input)
    

    enter image description here

    library(shiny)
    library(shinyvalidate)
    
    # Define ui ----
    ui <- fluidPage(
      uiOutput("registration_page")
    )
    
    # Define server ----
    server <- server <- function(input, output) {
      output$registration_page <- renderUI({
        fluidPage(
          textInput("regis_username", "Username:"),
          passwordInput("regis_password", "Password:"),
          passwordInput("regis_password2", "Re-enter password:"),
          actionButton("submit_registration", "Submit Registration")
        )
      })
      
      #Registration input validator
      reg_iv <- InputValidator$new()
      reg_iv$add_rule("regis_password", sv_required())
      reg_iv$add_rule("regis_password2", sv_required())
      reg_iv$add_rule("regis_password2", 
                      function(value, input){
                        if (value != input$regis_password) {
                          "Must match password"
                        } 
                      }, input)
      reg_iv$enable()
    }
    
    shinyApp(ui = ui, server = server)