I wrote the other day about overcoming an issue with Shiny.

Another issue I ran into concurrently was about file inputs. The issue was that file inputs (i.e., shiny::fileInput) was difficult to clear. That is, after a user uploads a file, it was easy to get some of the various parts cleared/cleaned up, but not others:

  • (Not Easy) The UI components of fileInput (the text of the file name, the loading display)
  • (Not Easy) The data behind the fileInput handler
  • (Easy) Displaying some feedback in the UI after handling file input

Load libraries

library(shiny)
library(shinyjs)
library(bslib)
library(DT)
library(vroom)

A helper function to handle reactive inputs

reactiveInput <- function(rval, path) {
  if (is.null(rval)) {
    return(NULL)
  } else if (rval == 'loaded') {
    return(path)
  } else if (rval == 'reset') {
    return(NULL)
  }
}

A bslib ui component

ui <- page_sidebar(
  title = "My dashboard",
  sidebar = list(
    actionButton("submit", "Submit"),
    actionButton("reset", "Reset")
  ),
  fileInput(inputId = "afile", "Upload file", accept = ".csv"),
  DT::DTOutput("result"),
  shinyjs::useShinyjs()
)

Here’s the server part that was giving me trouble.

server <- function(input, output) {
  observeEvent(input$submit, {
    output$result <- DT::renderDataTable({
      dat <- vroom::vroom(
        isolate(input$afile$datapath)
      )
      DT::datatable(dat)
    })
  })

  observeEvent(input$reset, {
    shinyjs::reset("afile")
    output$result <- renderText({})
  })
}

With shinyjs::reset we can reset the UI components of the file input handler, and with renderText with an empty input we can reset the UI feedback. However, the data backing the file input handler is not reset. This led to problems in the UI where you could keep pressing submit after clicking the Reset button because the data for the last uploaded file was still there, whereas the user should get an error that they need to upload a file before clicking Submit.

To be able to completey reset data behind the file input handler I found out about a solution using reactive values via stackoverflow. Basically, the change involves handling file input data through a reactive value and keeping track of the state of the file input loader.

Here’s the entire app that doesn’t work

Click to expand
library(shiny)
library(shinyjs)
library(bslib)
library(DT)
library(vroom)

ui <- page_sidebar(
  title = "My dashboard",
  sidebar = list(
    actionButton("submit", "Submit"),
    actionButton("reset", "Reset")
  ),
  fileInput(inputId = "afile", "Upload file", accept = ".csv"),
  DT::DTOutput("result"),
  shinyjs::useShinyjs()
)

server <- function(input, output) {
  observeEvent(input$submit, {
    output$result <- DT::renderDataTable({
      dat <- vroom::vroom(
        isolate(input$afile$datapath)
      )
      DT::datatable(dat)
    })
  })

  observeEvent(input$reset, {
    shinyjs::reset("afile")
    output$result <- renderText({})
  })
}

shinyApp(ui, server)

And here’s the entire app that does work

Click to expand
library(shiny)
library(shinyjs)
library(bslib)
library(DT)
library(vroom)

reactiveInput <- function(rval, path) {
  if (is.null(rval)) {
    return(NULL)
  } else if (rval == 'loaded') {
    return(path)
  } else if (rval == 'reset') {
    return(NULL)
  }
}

ui <- page_sidebar(
  title = "My dashboard",
  sidebar = list(
    actionButton("submit", "Submit"),
    actionButton("reset", "Reset")
  ),
  fileInput(inputId = "afile", "Upload file", accept = ".csv"),
  DT::DTOutput("result"),
  shinyjs::useShinyjs()
)

server <- function(input, output) {
  rv_file <- reactiveValues(file_state = NULL)

  thefile <- reactive({
    reactiveInput(rv_file$file_state, input$afile$datapath)
  })

  observeEvent(input$afile, {
    rv_file$file_state <- 'loaded'
  })

  observeEvent(input$submit, {
    output$result <- DT::renderDataTable({
      dat <- vroom::vroom(
        isolate(thefile())
      )
      DT::datatable(dat)
    })
  })

  observeEvent(input$reset, {
    shinyjs::reset("afile")
    rv_file$file_state <- 'reset'
    output$result <- renderText({})
  })
}

shinyApp(ui, server)