Home > other >  How to pass a R Shiny reactive expression as a default argument in a custom function
How to pass a R Shiny reactive expression as a default argument in a custom function

Time:01-16

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.

  • Related