Validating input file using shinyvalidate

I'm building an app that has an upload button. The button is a module, which I simplified for the purpose of the example here. I'd like to validate the input file using shinyvalidate, for example to ensure the file is a .csv. The logic of shinyvalidate lives outside of the upload module.

I can't seem to be able to access the actual file in the validation rule.

What's the recommended way to handle the validation in that case? Should I pass the validator to the module to perform the validation or is it possible to do it outside of the module?

Here's a simple app to illustrate what I'm trying to do.

library(shiny)
  
mod_upload_ui <- function(id, label = "Load data", ...) {
  ns <- NS(id)
  fileInput(
    ns("file"),
    label = label,
    buttonLabel = "Browse files",
    placeholder = "No files selected",
    ...
  )
}

mod_upload_server <- function(id) {
  moduleServer(id, function(input, output, session) {
    reactive(input$file)
  })
}

ui <- fluidPage(
  sidebarLayout(
    sidebar = sidebarPanel(
      mod_upload_ui("file")
    ),
    mainPanel(
      tableOutput("text")
    )
  )
)

server <- function(input, output, session) {
  iv <- shinyvalidate::InputValidator$new()
  
  # validate the input file
  iv$add_rule("file", function(x) {
    print(x) # prints NULL
  })
  
  iv$enable()
  
  file <- mod_upload_server("file")
  output$text <- renderTable({
    req(iv$is_valid())
    file()
  })
  
}

shinyApp(ui, server)

Part of the problem is that your fileInputid is file-file, not file because you have placed it inside the module. However if you use iv$add_rule("file-file" you get the error: Result of 'file-file' validation was not a single-character vector (actual class: data.frame) which seems to suggest that shinyvalidate isn't suitable to use with fileInput.

If you are just wanting to check it is a .csv then using accept = ".csv", in the fileInput goes a long way and you can double check by using tools::file_ext(input$file$datapath) == "csv" before trying to read the file. If there are more validation steps you want to do, it would be helpful to have more information about what they are.

Thanks for this.

Unfortunately, fileInput(accept =) doesn't seem to check the format of the file, at least when running the app locally.

I initially used the shinyFeedback package that I used inside the module to validate the file (in a similar fashion as you did with file_ext()) but I find it less convenient than shinyvalidate.

Just realized that fileInput(accept =) didn't work in my case because I didn't prefix the format with a dot… Using accept = "csv" doesn't work but accept = ".csv" does work.

Turns out I was wrong about shinyvalidate not being compatible. After some more fiddling (and some help from shiny assistant) here's an example:

library(shiny)

mod_upload_ui <- function(id, label = "Load data", ...) {
  ns <- NS(id)
  fileInput(
    ns("file"),
    label = label,
    buttonLabel = "Browse files",
    placeholder = "No files selected",
    accept = ".csv",
    ...
  )
}

mod_upload_server <- function(id) {
  moduleServer(id, function(input, output, session) {

    iv <- shinyvalidate::InputValidator$new()

    iv$add_rule("file", function(value) {
      if (is.null(value)) {
        return("Please upload a file")
      }

      if (tools::file_ext(value$name) != "csv") {
        return("Please upload a CSV file")
      }

      return(NULL)
    })

    iv$enable()

    list(file = reactive(input$file), valid = reactive(iv$is_valid()))
  })
}

ui <- fluidPage(
  sidebarLayout(
    sidebar = sidebarPanel(
      mod_upload_ui("file")
    ),
    mainPanel(
      tableOutput("text")
    )
  )
)

server <- function(input, output, session) {

  file_server <- mod_upload_server("file")
  output$text <- renderTable({
    req(file_server$valid())
    read.csv(file_server$file()$datapath)
  })

}

shinyApp(ui, server)
1 Like

Yep but in that case the validation is inside mod_upload_server(), which is ok but I wanted to keep it outside the module for flexibility purposes.

That said, you were right to point out that the correct id was file-file (and not file), so I could fix that to make it work in my example.

iv$add_rule("file-file", function(x) {
  if (!is.null(x) && tools::file_ext(x$name) != "csv") {
    "Must be a .csv file."
  }
})