1
votes

The module in the app below contains an observer that has two triggers:

  1. a reactive value input$trigger1 (the value of a fileInput that is rendered inside the module server)
  2. a reactive expression trigger2() (the expression returns the value of an actionButton in the main app that is fed to the module).

I would like the observer to print some text to the console when triggered by changes in either of these two values.

Attempt 1: I tried wrapping the triggers in a vector:

  observeEvent(c(input$trigger1, trigger2()), print('the one with the vector'), ignoreInit = T)

Problem: This observeEvent fires when the checkbox is checked.

I thought this might have something to do with the module's server being called instantaneously on app start-up whereas the module's UI is only rendered if input$check == TRUE? I tried investigating this by printing the values of input$trigger1 and trigger2() to the console using observe(). On start-up, the values printed to the console are trigger2(): 0 input$trigger1: NULL. When the checkbox is checked, input$trigger1 is still NULL but the observer fires again and prints input$trigger1: NULL to the console. Maybe this is due to trigger1 not existing in the DOM on start-up?

So I tried calling both the module's server and UI at the same time like so:

output$form = renderUI({req(input$check); modUI('mod')})

observeEvent(input$check, {
    req(input$check)
    callModule(modServer, 'mod', trigger2 = reactive(input$trigger2))
})

But the value of input$trigger1 still gets printed to the console twice, this time when the checkbox is checked. I don't know why because it's NULL on both occasions.


Attempt 2: According to a post in this thread it occurred to me that since input$trigger1 and trigger() have different classes, wrapping them in a vector might be the source of the problem (since R coerces them to the same class as pointed out by the linked post). So I tried using curly braces instead:

  observeEvent({input$trigger1; trigger2()}, print('the one with curly braces'), ignoreInit = T)

Problem: This observer responds to changes in trigger2() but not to changes in input$trigger1. Maybe this has something to do with the return value of the eventExpr (trigger2()) not changing if input$trigger1 changes, but since the the eventExpr is a reactive expression, a change to either input$trigger1 or trigger2() should invalidate it to trigger the observer as in this post.


Attempt 3: I tried swapping the order of the triggers:

  observeEvent({trigger2(); input$trigger1}, print('the one with curly braces with the order swapped around'), ignoreInit = T)

Problem: This time the observer responds to both triggers but only if input$trigger1 is changed first. Otherwise, it is unresponsive.


Here is the reproducible code:

library("shiny")

# MODULE UI ---------------------------------------------------------------
modUI = function(id) {
  ns = NS(id)
  uiOutput(ns('ui'))
}

# MODULE SERVER -----------------------------------------------------------
modServer = function(input, output, session, trigger2) {
  
  ns = session$ns
  
  output$ui = renderUI(fileInput(ns('trigger1'), 'Trigger1'))
  
  #OBSERVER 1 ---------------------------------------------
  observeEvent(c(input$trigger1, trigger2()), print('the one with the vector'), ignoreInit = T)

  # observe(print(list(`trigger2 (actionButton)` = trigger2())))
  # observe(print(list(`trigger1 (fileInput)`= input$trigger1)))
  
  #OBSERVER 2 ---------------------------------------------
  # observeEvent({input$trigger1; trigger2()}, print('the one with curly braces'), ignoreInit = T)

  #OBSERVER 3 ---------------------------------------------
  # observeEvent({trigger2(); input$trigger1}, print('the one with curly braces with the order swapped around'), ignoreInit = T)
  
}


# MAIN APP ----------------------------------------------------------------

ui <- fluidPage(
  checkboxInput('check', 'Show'),
  uiOutput('form'),
  actionButton('trigger2', 'Trigger 2')
)

server <- function(input, output, session) {
 
  output$form = renderUI({req(input$check); modUI('mod')})
  
  # observe({
  #   req(input$check)
  #   callModule(modServer, 'mod', trigger2 = reactive(input$trigger2))
  # })
  
  callModule(modServer, 'mod', trigger2 = reactive(input$trigger2))
  
}

shinyApp(ui = ui, server = server)

I've tried everything I can think of to isolate the issue to no avail so any and all guidance would be very much appreciated.

UPDATE: Maybe it has something to do with the value of input$file being a data.frame? I tried replacing input$trigger1 with trigger1(), a reactiveVal that increments by 1 each time input$trigger1 changes. This way both triggers have the same class and length (i.e. numeric(1)). The resulting observer works as expected:

modServer = function(input, output, session, trigger2) {

  ns = session$ns

  trigger1 = reactiveVal(0)

  observeEvent(input$trigger1, trigger1(trigger1()+1))

  observeEvent({trigger2(); trigger1()}, print('hello'), ignoreInit = T)

}

I would still like to understand why attempts 1-3 didn't work though so if anyone has any insight, please post a reply.


Explanation: As MattB pointed out, the actionButton initialises as 0 and the fileInput initialises as NULL. This means that the eventExpr for the observers in attempt 2 and 3 will evaluate to NULL as long as the input value on the last line of the expression is NULL. The eventExpr of the example in the linked post consists of a numericInput and a textInput. Since these initialise with values 0 and '' respectively, the eventExpr never returns NULL and the observer is always responsive to both inputs regardless of which is changed first.

1
Have you tried wrapping the trigger events in a list?Bertil Baron
Thank you Bertil, that almost works! Wrapping the triggers in a list makes the observer responsive to input$trigger1 and trigger2() as desired but it also causes it to fire when the checkbox is checked :( Any idea on why that's happening? Setting ignoreInit == T doesn't seem to help.user51462

1 Answers

1
votes

observeEvent has ignoreNULL = TRUE set by default. As you correctly suspect, while the observer will trigger for a change to any part of {x;y}, it will only do that if the value of that whole expression is not NULL. In this example, as long as y is NULL, the observer won't trigger, no matter what you do to x. This is easily fixed by setting ignoreNULL = FALSE.