I have a Shiny app that calls several custom functions in response to a click event. These custom functions make use of multiple reactive values and I didn't think I would need to pass all of those reactive values as arguments to the custom functions but it seems like I do.
I would have expected the app to behave like normal R where a custom function will search the immediate environment, then, upon not finding a variable, will search the enclosing environment and up the scope, only throwing an error if that variable's undefined at every level. Instead, when the function deals with reactive variables, it seems like code within the function is unaware of reactive variables defined outside of it. Is this true?
A quick demo app that crashes because identity_fun cannot find input$click:
library(shiny)
identity_fun <- function(x){
print(paste("Title's been changed", input$click, "times now"))
x
}
ui <- fillPage(
sidebarLayout(
sidebarPanel(
textInput("text", "Plot title here"),
actionButton("click", "Click when ready to apply it")
),
mainPanel(
plotOutput("mainplot")
)
)
)
server <- function(input, output){
input_text <- reactiveVal()
observeEvent(input$click, {
input_text(identity_fun(input$text))
})
output$mainplot <- renderPlot({
plot(1, main = input_text())
})
}
shinyApp(ui, server)
In base R, a variable outside the function is found trivially:
input <- list()
input$click <- 1
identity_fun("blah")
[1] "Title's been changed 1 times now"
[1] "blah"
and this different behavior took me by surprise when working with a Shiny app.
To fix the above app, I can pass the relevant information as an argument to identity_fun
identity_fun <- function(x, input_click){
print(paste("Title's been changed", input_click, "times now"))
x
}
and
observeEvent(input$click, {
input_text(identity_fun(input$text, input$click))
})
but I'm wondering if that's the best way of doing it. I realize this is probably intentional behavior because it seems complicated for the function to auto-detect that it uses input$click and invalidate if input$click changes, but Shiny's been magic to me before.
Is there a better way of passing reactive values to a function than by adding them as arguments?