Validate field to allow file upload - files are uploading twice

I'm trying to build a form that collects an identification number and allows file uploads to that id.

The id number has a valid form (5 or 6 digits). I have the following code, which works as expected except when an invalid number is entered, and a file is uploaded anyway.

The file isn't uploaded (good), and the "invalid" message appears. Unfortunately, when a valid number is then entered, two directories are created, and the file is uploaded twice.

I'm new to reactivity and shiny, and some of this code was copy/pasted off the web. I've obviously done something wrong and would appreciate guidance on how to solve the issue.

I can imagine two ways forward. I'm unsure which way to go - either fixing the following code so that a file is only uploaded once or requiring id number validity before allowing the upload field to be used or seen. Advice on best practices for this end would also be appreciated.

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

  iv <- InputValidator$new()
  iv$add_rule("identifier",sv_regex("^#?[0-9]{5,6}$", "Please input an id number and try again"))

  output$contents <- renderTable({

    req(input$file)

    observeEvent(input$file, {
      if(iv$is_valid()) {
      reset_form()
      removeNotification("submit_message")

      #create new folder in shared Google Drive with id number
      newdr <- drive_mkdir(paste(input$identifier), path = drive_id) 
      new_path <- newdr$id

      mapply( function(datapath, name){
        datetime = format(as.POSIXct(Sys.time()),"%Y-%m-%d-%H:%M:%S")
        new_name = paste(datetime,input$file$name,sep="-")
        drive_upload(media = datapath,
                     name = new_name,
                     path = new_path)},
        input$file$datapath,
        input$file$name)
      }
      else {
        iv$enable()
        showNotification(
          "Please correct the errors in the form and try again",
          id = "submit_message", type = "error")
      }
    })
    reset_form <- function() {
      iv$disable()
      removeNotification("submit_message")
      updateTextInput(session, "identifier", value = "")
    }
    return()
  })
}

I have tried using code similar to that found on the post insertUI/removeUI based on a checkboxInput to insert the file upload field on is_valid() but it's not working either - the file input field flashes up and disappears. I don't understand why that is happening.

server <- function(input, output, session) {
  UI_exist = FALSE

  iv <- InputValidator$new()
  iv$add_rule("identifier",sv_regex("^#?[0-9]{5,6}$", "Please input an id number and try again"))

output$contents <- renderTable({
  
  req(input$file)
  
  observeEvent(input$identifier, {
      if(iv$is_valid()) {
        reset_form()
        removeNotification("submit_message")
        insertUI(
          selector = "#fileinput",
          where = "afterBegin",
          ui = fileInput("file", "Select the files to upload", multiple = TRUE),
        )
        UI_exist <<- TRUE
      }
      else {
        iv$enable()
        showNotification(
          "Please correct the errors in the form and try again",
          id = "submit_message", type = "error")
        removeUI(selector = "div#fileinput > div")
        UI_exist <<- FALSE
      }
    })
    reset_form <- function() {
      iv$disable()
      removeNotification("submit_message")
      updateTextInput(session, "identifier", value = "")
    }    
    
    observeEvent({input$identifier; input$file}, {
      if(iv$is_valid() && UI_exist) {
        #create new folder in shared Google Drive with id 
        newdr <- drive_mkdir(paste(input$identifier), path = drive_id) 
        new_path <- newdr$id

        mapply( function(datapath, name){
          datetime = format(as.POSIXct(Sys.time()),"%Y-%m-%d-%H:%M:%S")
          new_name = paste(datetime,input$file$name,sep="-")
          drive_upload(media = datapath,
                     name = new_name,
                     path = new_path)},
          input$file$datapath,
          input$file$name)
      }

    return()

    })
  })
}

Could you format this into a reproducible example? That is a set of code or rstudio.cloud project that folks can easily get up and running to replicate your issue? Currently, this is only part of a shiny app.

IF you aren't familiar with best practices for shiny reprexes, check out

This will make it easier for folks to replicate your issue and offer suggestions to solve it.

Of course, apologies. Here it is with the google drive auth and other unnecessary stuff taken out:

library(shiny)
library(shinyvalidate)
library(googledrive)

#250MB max limit per upload
options(shiny.maxRequestSize=250*1024^2) 

#setting a service account token for Google Drive access
drive_auth(
  path = ""
)

drive_id <- as_id("")

# Define UI for data upload app ----
ui <- fluidPage(
  
  tags$head(tags$style(HTML(".shiny-output-error-validation { color: red; }"))),
  
  sidebarLayout(

    mainPanel(
    
      div(id="identifier",
        textInput("identifier", "Id #", placeholder = "Input ID number"),
      ),
      div(id="fileinput"),
      tableOutput("contents")
    )
  )
)
server <- function(input, output, session) {

  iv <- InputValidator$new()
  iv$add_rule("identifier",sv_regex("^#?[0-9]{5,6}$", "Please input an id number and try again"))

  output$contents <- renderTable({

    req(input$file)

    observeEvent(input$file, {
      if(iv$is_valid()) {
      reset_form()
      removeNotification("submit_message")

      #create new folder in shared Google Drive with id number
      newdr <- drive_mkdir(paste(input$identifier), path = drive_id) 
      new_path <- newdr$id

      mapply( function(datapath, name){
        datetime = format(as.POSIXct(Sys.time()),"%Y-%m-%d-%H:%M:%S")
        new_name = paste(datetime,input$file$name,sep="-")
        drive_upload(media = datapath,
                     name = new_name,
                     path = new_path)},
        input$file$datapath,
        input$file$name)
      }
      else {
        iv$enable()
        showNotification(
          "Please correct the errors in the form and try again",
          id = "submit_message", type = "error")
      }
    })
    reset_form <- function() {
      iv$disable()
      removeNotification("submit_message")
      updateTextInput(session, "identifier", value = "")
    }
    return()
  })
}
shinyApp(ui, server)

Is google drive a necessary part of your issue ? Would you still have the problem if you made a local directory in tempdir() instead?

Google Drive is a necessary part of the app because of the size of the files expected and the app will be public facing.

I can try with tempdir() but I'm guessing that will only show if there is a bug in the googledrive package. I have a lot more confidence in the googledrive code base than I do with my app :slight_smile:

My assumption is that you dont have a google drive problem per se, so your reprex will be more tractable and easier for others to run without it. Thats the basis of my suggestion anyway.

Hi Lachlan!

I haven't run the code you provided yet, however I did take a look at it.

I may have not undestood the underlying idea :sweat_smile: , but to me it looks a bit strange that you have an observeEvent and a function definition reset_form inside the renderTable function.
Are there any specific resons for that?

I think this might be a likely candidate for the 2x upload issue you mention.

Hope this helps :slight_smile:
Cheers!


This post was published by an Appsilon team member. Our company can help you get the most out of RShiny and Posit/RStudio products.

Check our open positions here.

Appsilon: Building impactful RShiny Dashboards and providing R coding services.
Appsilon_GIFsmall_whitebg

@agus yes, you are absolutely right - I don't know why. I presume because I copy/pasted someone who also didn't know what they are doing. I made this realisation on Monday but haven't had a chance to re-write the code. I'm doing a rewrite and will update with an explanation once I'm done.

Ok, after advice given above, I've changed the structure around a bit - adding and removing the file input fields after the id number has been validated. Note that this works on local machine. The recommendation to get it working locally was useful - thanks @nirgrahamuk and @agus for your prompts.

library(shiny)
library(shinyvalidate)

ui <- fluidPage(
  
  tags$head(tags$style(HTML(".shiny-output-error-validation {color: red;}"))),

  sidebarLayout(

    mainPanel(
      
      div(id="identifier",
          textInput("identifier", "ID #", placeholder = "Input ID number"),
          actionButton("idsubmit","Submit")
      ),

    )
  )
)

server <- function(input, output, session) {
  
  iv <- InputValidator$new()
  iv$add_rule("identifier",sv_regex("^#?[0-9]{5,6}$", "Please input an id number and try again"))
  
  values <- reactiveValues(
    id_state = NULL
  )
  
  reset_form <- function() {
    iv$disable()
    removeNotification("submit_message")
    updateTextInput(session, "identifier", value = "")
  }
  
  observeEvent(input$idsubmit, {
    if(iv$is_valid()) {
      insertUI(
        selector= "#idnumber",
        where="beforeEnd",
        ui = div(id="resetButton", actionButton("resetID","Reset ID Number")),
      )
      insertUI(
        selector= "#idnumber",
        where="afterEnd",
        ui = div(id="fileinput", fileInput("file", "Select the files to upload", multiple = TRUE)),
      )
      values$id_state <- 'valid'
    }
    else {
      iv$enable()
      showNotification(
        "Please correct the errors in the form and try again",
        id = "submit_message", type = "error")
      values$id_state <- NULL
      }})
  
  observeEvent(input$resetID, {
      values$id_state <- NULL
      iv$enable()
      updateTextInput(session, "identifier", value = "")
      removeUI(selector="div#fileinput")
      removeUI(selector="div#resetButton")
      })

  observeEvent(input$file,
    if(values$id_state == "valid") {
      removeUI(selector="div#resetButton")
      removeUI(selector="div#fileinput")
      reset_form()
      removeNotification("submit_message")
      
      newdir <- paste("C:/Users/username/upload",input$identifier,sep="/")
      dir.create(newdir)
      
      datetime = format(as.POSIXct(Sys.time()),"%Y-%m-%d")
      new_name = paste(datetime,input$file$name,sep="-")
      file.copy(from = input$file$datapath,
                to = newdir)
      file.rename(paste(newdir,basename(input$file$datapath),sep="/"),paste(newdir,new_name,sep="/"))
      })}

shinyApp(ui, server)

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.