0

I have a shiny app that generates various tables of data and each is triggered by an action button in the UI and an observe event in the server. I am trying to add several download handlers so users can download the data tables as csv files. In my app, when I push on the download button, the app downloads "download.html" with the content of the app UI. I have checked the names of my button ID and it matches my download handler so it is not the problem as discussed here: https://stackoverflow.com/a/42583662/7565465

My app code is long so I have made a toy example that illustrates more or less how the app works. I have data frames that are created inside an observe event when an action button is clicked. In my toy example below, the download handler 1) does not assign the right name; it assigns d1.txt or d2.txt (rather than data1.csv and data2.csv) respectively. 2) The file will not open and chrome tells me "Failed - Server problem." I recognize that this is not the exact behavior of what happens when I go to download in my actual app (i.e., the "download.html" issue) but I assume that the two are similar, and I am hoping that if someone can help me understand what is wrong with my toy example below, that I can get on the right path to solving my problems. Thanks!!!

ui <- fluidPage(

  # App title ----
  titlePanel("Hello Shiny!"),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      actionButton("button1", "click me 1"), br(), 

      actionButton("button2", "click me 2"), br(),

      downloadButton("d1", "Download Data 1"), br(),

      downloadButton("d2", "Download Data 2")

    ), 

      mainPanel(

        helpText("Tables will apear when you push 'click me'"),

        br(),

       tableOutput("table1"), 

       br(), 

       tableOutput("table2")

    )))

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

  observeEvent(input$button1, {

   mydata1 <- data.frame (col1 = c(1:4),
                         col2 = letters[1:4], 
                         stringsAsFactors = FALSE)

   output$table1 <- renderTable( mydata1)

  })#close observerEvent1

  observeEvent(input$button2, {

    mydata2 <- data.frame (col1 = c(5:8),
                           col2 = letters[5:8], 
                           stringsAsFactors = FALSE)

    output$table2 <- renderTable( mydata2)

  }) #close observerEvent2

  #download data 1
    output$d1 <- downloadHandler(
    filename = function() {
      "data1.csv"
    },
    content = function(file) {
      write.csv(mydata1, file, row.names = FALSE)
    }
  )

  #download data 2
    output$d2 <- downloadHandler(
    filename = function() {
      "data2.csv"
    },
    content = function(file) {
      write.csv(mydata2, file, row.names = FALSE)
    }
  )

}

#open directly in browser
runApp(list(ui = ui, server = server), launch.browser = T)
jesse
  • 1
  • 3
  • This is not how `observe` is intended to be used. You should instead use `mydata1 <- eventReactive(input$button1, { data.frame(...); })` and `output$table1 <- renderTable( mydata() )`. – r2evans May 13 '20 at 20:50

2 Answers2

0

The preferred/recommended flow is to create data within a reactive (or eventReactive) block, and then react to that data changing. Trying to change any output$ from within an observe block will not work right.

Because we will shift to reactive data, that means you need to refer to the reactive component of this data, which looks like a function call. That is, if mydata1 is my reactive data, then whenever you want to view the data, you must use mydata1().

Your code, adapted to this model:

ui <- fluidPage(
  # App title ----
  titlePanel("Hello Shiny!"),
  # Sidebar layout with input and output definitions ----
  sidebarLayout(
    # Sidebar panel for inputs ----
    sidebarPanel(
      actionButton("button1", "click me 1"), br(), 
      actionButton("button2", "click me 2"), br(),
      downloadButton("d1", "Download Data 1"), br(),
      downloadButton("d2", "Download Data 2")
    ), 
    mainPanel(
      helpText("Tables will apear when you push 'click me'"),
      br(),
      tableOutput("table1"), 
      br(), 
      tableOutput("table2")
    )))

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

  mydata1 <- eventReactive(input$button1, {
    data.frame(col1 = c(1:4),
               col2 = letters[1:4], 
               stringsAsFactors = FALSE)
  })
  output$table1 <- renderTable( mydata1() )

  mydata2 <- eventReactive(input$button2, {
    data.frame(col1 = c(5:8),
               col2 = letters[5:8], 
               stringsAsFactors = FALSE)
  })
  output$table2 <- renderTable( mydata2() )

  #download data 1
  output$d1 <- downloadHandler(
    filename = function() {
      "data1.csv"
    },
    content = function(file) {
      write.csv(mydata1(), file, row.names = FALSE)
    }
  )

  #download data 2
  output$d2 <- downloadHandler(
    filename = function() {
      "data2.csv"
    },
    content = function(file) {
      write.csv(mydata2(), file, row.names = FALSE)
    }
  )

}

#open directly in browser
runApp(list(ui = ui, server = server), launch.browser = T)
r2evans
  • 141,215
  • 6
  • 77
  • 149
  • Thank you @r2evans. Ok, I understand how this new work flow would work. Is it possible, however, to simply add a reactive version of the table, or something like that, to my existing workflow that uses observeEvent? I have a pretty comprehensive workflow in my app and would like to keep using observeEvent. – jesse May 14 '20 at 20:00
  • Do not update `output$` things from an `observe` block. I don't know if it can be done, but the documentation and tutorials (from the `shiny` devs) clearly do not use this method. – r2evans May 14 '20 at 23:01
  • The premise of a reactive `output$` is that it changes whenever one of its dependent reactive sources changes, so whatever triggers an `observe` block can also be used to trigger a reactive output. The biggest trouble I have put myself in has resulted from over-dependency. For instance, block "B" depending on "A", and "C" depending on both "A" and "B", I found over-dependency cause "C" to trigger too many times. Really, just keep `output$... <- ...` outside of `observe` blocks, it's not intended to work that way. – r2evans May 14 '20 at 23:13
  • Thank you @r2evans. I'll play around with my app using the guidelines you suggested. – jesse May 15 '20 at 11:27
0

While I believe that @r2evans code is probably the preferable way to write one's script, the observeEvents in my code trigger a number of processes on the generated data frames and I wanted to keep the original structure of the code. I was able to get the download handler to read the data from within the observeEvenets by creating a reactive list outside of the observeEvent, then appending a 'reactive' copy of the data frame to the list inside the observeEvent, and calling this reactive copy in the download handler. I admit that it involves replicating objects and extra steps, but it allowed me to keep the original structure of my app, which does a lot more than the toy example I posted here. Maybe this could be a good workaround for others?

library(shiny)
ui <- fluidPage(

  # App title ----
  titlePanel("Hello Shiny!"),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      actionButton("button1", "click me 1"), br(), 

      actionButton("button2", "click me 2"), br(),

      downloadButton("d1", "Download Data 1"), br(),

      downloadButton("d2", "Download Data 2")

    ), 

      mainPanel(

        helpText("Tables will apear when you push 'click me'"),

        br(),

       tableOutput("table1"), 

       br(), 

       tableOutput("table2")

    )))

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

  #make an empty reactive list **outside of observe events** to append reactive datasets too. 
  reactiveData<-reactiveValues() 

  #observeEvent 1
   observeEvent(input$button1, {

   mydata1 <- data.frame (col1 = c(1:4),
                          col2 = letters[1:4], 
                          stringsAsFactors = FALSE)

   reactiveData$one<-mydata1

   output$table1 <- renderTable( mydata1)

  })#close observerEvent1

   #observeEvent 2
  observeEvent(input$button2, {

    mydata2 <- data.frame (col1 = c(5:8),
                           col2 = letters[5:8], 
                           stringsAsFactors = FALSE)

    reactiveData$two<-mydata2

    output$table2 <- renderTable( mydata2)

  }) #close observerEvent2

  #download data 1
    output$d1 <- downloadHandler(
    filename = function() {
      "data1.csv"
    },
    content = function(file) {
      write.csv(reactiveData$one, file, row.names = FALSE)
    }
  )

  #download data 2
    output$d2 <- downloadHandler(
    filename = function() {
      "data2.csv"
    },
    content = function(file) {
      write.csv(reactiveData$two, file, row.names = FALSE)
    }
  )

}

#open directly in browser
runApp(list(ui = ui, server = server), launch.browser = T)
jesse
  • 1
  • 3