I am trying to pass input$myInput
text field to a custom function as a predefined argument. This is important to control the functions arguments in future for documentation (roxygen2).
Here is a minimal working example:
The app schould bind the input field to mtcars dataset and apply the custom function my_DTarguments
with two arguments (1. data, 2. input):
library(shiny)
library(DT)
#function
my_DTarguments <- function(data, input=input$myInput) {
DT::datatable(
rownames = FALSE,
data,
extensions = 'Buttons',
options = list(
dom = 'frtipB',
buttons = list(
list(
extend = 'csv',
filename = paste0("mydesiredname-", "cyl-", input, "-", Sys.Date())
),
)
)
)
}
shinyApp(
ui <- fluidPage(
textInput(inputId = "myInput", label = "My Input", placeholder = "a"),
DT::dataTableOutput("table")
),
server <- function(input, output) {
# combine mtcars with input field
mtcars1 <- reactive({
cbind(mtcars, input$myInput)
})
# apply function to mtcars1
output$table <- DT::renderDataTable(
my_DTarguments(mtcars1())
)
})
}
)
The last error after many tries is : promise already under evaluation: recursive default argument reference or earlier problems?
CodePudding user response:
The thing with reactive expressions is that Shiny adds them to the reactive chain and then evaluates all reactive expressions in the chain one by one. We do not know the order in which Shiny does that.
In your case, Shiny tries to evaluate renderDataTable
and by doing so calls my_DTarguments()
. However, myInput
has not been evaluated, yet, hence the error.
When you use a reactive expressions as a default function argument, you should always add a in a req(...)
call (example: req(input$myInput)
). But you still have to add input$myInput
in the call to my_DTarguments()
from renderDataTable
. But you'll get an empty column, if input$myInput
is still empty.
Alternatively, you can make sure that input$myInput
is truthy before calling my_DTarguments
. In this case, the table will only be shown once input$myInput
is not empty and Shiny has already evaluated it.
Personally I think the secnond is the cleaner approach. I recommend that we do not use reactive expressions as default arguments for functions, in general. Not only does it violate the idea of a default argument when the caller has to add it anyway. The way reactive expressions work they are only available in the runtime environment as needed. A default argument, however, should be available anytime a function is called. That is like adding a predetermined breaking point in our code ... which - of course - we don't want.
library(shiny)
library(DT)
#function
my_DTarguments <- function(data, input=req(input$myInput)) {
DT::datatable(
rownames = FALSE,
data,
extensions = 'Buttons',
options = list(
dom = 'frtipB',
buttons = list(
#list(
extend = 'csv',
filename = paste0("mydesiredname-", "cyl-", input, "-", Sys.Date())
#),
)
)
)
}
shinyApp(
ui <- fluidPage(
textInput(inputId = "myInput", label = "My Input", placeholder = "a"),
DT::dataTableOutput("table")
),
server <- function(input, output) {
# combine mtcars with input field
mtcars1 <- reactive({
cbind(mtcars, input$myInput)
})
# apply function to mtcars1
output$table <- DT::renderDataTable({
my_DTarguments(mtcars1())
# Alternative approach: call req() here
# my_DTarguments(mtcars1(), req(input$myInput))
})
}
)
By the way, there seems to be an issue with the buttons option. I commented 2 lines out, so that the code runs.