In short: If downloadHandler is used directly inside the server function, then the download works as expected; but if I use downloadHandler in a separate script in a modular way (inside a moduleServer), then when I download a file it works the 1st time but then if I update the table and try to download the new table, the new downloaded file still contains the info of the 1st table. So as soon as I download the data for the 1st time, regardless of which filtering or grouping I do in the app afterwards, when I try to download the "new" table it will always download the same original downloaded table.
PD: This is not just true for .xlsx files but also for .csv files using write.csv()
Detailed: I always go for the module approach when building shiny apps but I have encounter a problem for some time now when building the downloadHandler module. For this module, I have an independent script with the following functions:
download_button_UI <- function(id, label = "", class = "btn-primary btn-xs", ...) {
ns <- NS(id)
downloadButton(ns("down_button"),
label = label,
icon = icon("download"),
class = class,
title = "Download",
...)
}
download_button_Server <- function(id, df, name, ...) {
require(openxlsx)
moduleServer(id,
function(input, output, session) {
output$down_button <- downloadHandler(
filename = function() name,
# content = function(file) fwrite(df, file)
content = function(file) openxlsx::write.xlsx(df, file)
)
}
)
}
Then I use it in the UI and Server respectively:
# its a dashboardPage so the UI is in the "body" part of the page
body <- dashboardBody(
# some body parts
# Analysis table output
download_button_UI("down_analysis", "")
# some more body parts
)
server <- function(input, output, session) {
# some code
download_button_Server(id = "down_analysis", df = analysis_tbl()$df, name = "analysis.xlsx")
# some more code
}
This works as expected for the 1st download of the data; but then If I change the filters and grouping/aggregation of my data and try to download the "new" updated table, the resulting .xlsx file still contains the info of the 1st downloaded table.
If I use the downloadHandler directly inside the server function, then there is no issue and the last updated table is downloaded:
# its a dashboardPage so the UI is in the "body" part of the page
body <- dashboardBody(
# some body parts
# Analysis table output
downloadButton("down_analysis", "")
# some more body parts
)
server <- function(input, output, session) {
# some code
output$down_analysis <- downloadHandler(
filename = "analysis.xlsx",
content = function(file) write.xlsx(summ_tbl()$df, file)
)
# some more code
}
Then the code works as expected.
Of course you might say that then I should just use the downloadHandler inside the server but I have several tables and download buttons in the app so I don't want to be repeating the same code over and over, and the module solution is simply always better and cleaner.
I don't know if this is a shiny bug and if somebody else has encounter a similar problem; I have not found this issue mentioned in any other site and as I said, I encountered it a couple of years ago and it is still persisting so I would like to solve it once and for all.
You've passed the value via analysis_tbl()$df
to the server module, which means that no update will be registered. You may want to pass in the entire reactive value analysis_tbl
. Calling analysis_tbl()
then should always give the most up-to-date values (see, i.e., this answer).
Your module server then could look like this:
# passing analysis_tbl reactive instead of df itself
download_button_Server <- function(id, analysis_tbl, name, ...) {
require(openxlsx)
moduleServer(id,
function(input, output, session) {
df <- analysis_tbl()$df
output$down_button <- downloadHandler(
filename = function() name,
content = function(file) openxlsx::write.xlsx(df, file)
)
}
)
}
And for your app:
server <- function(input, output, session) {
# some code
output$down_analysis <- downloadHandler(
filename = "analysis.xlsx",
content = function(file) write.xlsx(summ_tbl, file)
)
# some more code
}