ObserveEvent issues in shiny modules.

Hi
Hoping the everyone is doing good in these tough times.

In my demo app the observeEvent when run from the module server side updates the numeric value instantly even when I am using ignoreInit = TRUE . This argument is working fine in Shiny apps without the shiny modules.
I ran into a this trivial problem while using observeEvent from module server which I am not understanding why? Can anyone explain the reason for such differential behavior in Shiny apps?

Below is my Demo code

       library(shiny)
       library(shinyjs)
      # Module UI

          ui_module <- function(id){
               ns <- NS(id)
                     fluidRow(
                           selectInput(ns("Year"),"Year",c(2020:2050)),
                           uiOutput(ns("Input_ui")),
             # Default value define in ui
                 tags$div(id = ns("Def_id") ,numericInput(ns("default"),"Deafault",NA,value = 
                             2423,step = 1))
                   )
           }

      # Module Server

              server_module <- function(input,output,session){

          # Default value while rendering ui.

            output$Input_ui <- renderUI({
                        ns <- session$ns
                       numericInput(ns("Input"), "Number", NA, value = 537153, step = 1)
            })

        # Values update anyhow at the start even if I am using ignoreInit = TRUE
        # The argument works in Apps without the shiny modules but not here. WHY???

               observeEvent(input$Year,{
                            updateNumericInput(session,
                                         "Input", "Number",value = 4, step = 1)

                            updateNumericInput(session,
                                          "default", "Deafault",value = 60, step = 1)},ignoreInit = TRUE)
                         }

         # App UI

                ui <- fluidPage(

                       inlineCSS(".row {margin:60px 0;}
                       div[id *= 'input_div']{position:absolute;left:26em;width:200px;height:30px;}
                       div[id *= 'input2_div']{position:absolute;left:20em;width:200px;height:30px;
                       div[id *= 'Def_id']{position:absolute;left:30em;width:200px;height:30px;"),

            fluidRow(id = "Row",
                      uiOutput("ui"),
                      actionButton("add","ADD")
                   )
              )

       # APP Server

                server <- function(input,output,session){

                   #Counter

                   n <- 0

         # One by one adding the modules

              observeEvent(input$add,{
                          n <<- n + 1
                          panels <- paste0("panels_new",n)
                           insertUI("#Row",
                           "beforeEnd",
                           ui_module(panels))

                 callModule(server_module,panels)
           })

              # Generating a no of shiny modules based on the some table rows
              # n will be nrow in my main app.

             output$ui <- renderUI({
                         n <- 2
                         list <- as.list(1:n)
                         lapply(list, function(i){
                         panels <- paste0("panels",i)
                                  fluidRow(
                                        ui_module(panels)
                                        )
                               }) 
                 })

             observe({
                      n <- 2
                     list <- as.list(1:n)
                     lapply(list, function(i){
                     panels <- paste0("panels",i)
                     callModule(server_module,panels)
                 })
            })
       }

    shinyApp(ui,server)

Thanks

Jitu

The input$Year expression doesn't work well with observeEvent in Shiny module. So, replacing it with action button however does the job perfectly. But in my case I need to see change without clicking any button and default values changes only on year change. It seems that making a list of independent but similar input panels doesn't work with the input$Year exp which is not the case with a single input panels. I tried to observeEvent without involving the shiny modules by directly making a list of input panels in my App server but there also the results with input$Year is the same.

Anybody having any idea that why this particular behavior with the input panel list where the only eventExpr working perfectly well is the action button and not any other expression like in my case input$Year

I didn't read your code, but from the description of your symptoms it sounds like https://mastering-shiny.org/scaling-modules.html#server-inputs might be the problem — you need to pass a reactive() to a module, otherwise it just uses the value of the input at the time the module was created.

I tried passing the reactive year from the main app server to the module server as. Below are the codelines from my example code only

APP Server

     output$ui <- renderUI({
          lapply(list, function(i){     
            panels <- paste0("panels",i)
            Year_App <- paste0("Year_App",i)
                             fluidRow(
                                      ui_module(panels),
                                      selectInput(Year_App,"",c(2020:2050))        
                                 )
                               })
                             })    

     lapply(list,function(i){
      Year_App <-  reactive({input[[paste0("Year_App",i)]]}  # idvar from the App server 
       panels <- paste0("panels",i)
       callmodule(server_module,panels,Year_App)
     })

Module Server

 server_module <- function(input,output,session,Year_App){

  # Now observing the event as follows 
  
      observeEvent(Year_App(),{
                   updateNumericInput(session,
                                     "Input", "Number",value = 4, step = 1)            
              },ignoreInit = TRUE)

      # Also tried

             Year <- reactive(input$Year)          # idvar defined in moduleUI
      
             observeEvent(Year(),{
                            updateNumericInput(session,
                                              "Input", "Number",value = 4, step = 1)
                               },ignoreInit = TRUE)

       # Still my problems persisted.

Even after wrapping my Year var inside a reactive function the ignoreInit = TRUE is not working as expected.

I can't run that code easily. If you want help, I'd recommend following the advice in https://mastering-shiny.org/action-workflow.html#getting-help.

Sure, Thanks. I will do the needful

I am giving you a reprex of my example so that you can run it easily. Please find the reprex below

library(shiny)
library(shinyjs)

 # Module UI

ui_module <- function(id){
 ns <- NS(id)
     fluidRow(
       tags$div(id = ns("Year_Mod_Div"),selectInput(ns("Year"),"Year_Module",c(2020:2050))),
   
# Numeric Inputs with default values
       tags$div(id = ns("Def1_id") ,numericInput(ns("default1"),"Deafault1",NA,value = isolate(26542),step = 1)),
       tags$div(id = ns("Def2_id") ,numericInput(ns("default2"),"Deafault2",NA,value = isolate(89609),step = 1))
       # actionButton(ns("action"), "Update"),
       )
}

 # Module Server

server_module <- function(input,output,session,Year_App){

  Year_Mod <- reactive(input$Year) # Year input wrapped in reactive function

  # Values update anyhow at the start even if I am using ignoreInit = TRUE
  # The argument works if I am not making a list of panels. WHY???

 observeEvent(Year_App(),{
      updateNumericInput(session,
                   "default1", "Deafault1",value = 80, step = 1)
      updateNumericInput(session,
                   "default2", "Deafault2",value = 40, step = 1)
   },ignoreInit = TRUE)  
}

 # App UI

ui <- fluidPage(

   inlineCSS(".row {margin:50px 0;}
              div[id *= 'Year_Mod_Div']{position:absolute;margin-top:-5em;left:2em;width:80px;height:30px;}
              div[id *= 'Yr_App_Div']{position:absolute;margin-top:-8.5em;left:12em;width:80px;height:30px;}
              div[id *= 'Def1_id']{position:absolute;left:2em;width:80px;height:30px;}
              div[id *= 'Def2_id']{position:absolute;left:8em;width:80px;height:30px;"),

  fluidRow(id = "Row",
         uiOutput("ui") 
   )
)

# APP Server

     server <- function(input,output,session){  
          #Counter  
            n <- 0

        # Generating a no of shiny modules based on the some table rows
        # n will be nrow in my main app.

     output$ui <- renderUI({
                  n <- 3
                  list <- as.list(1:n)
                  lapply(list, function(i){     
                     panels <- paste0("panels",i)
                     Year <- paste0("Year_App",i)
                     Year_div <- paste0("Yr_App_Div",i)
                        fluidRow(
                                ui_module(panels),
                                tags$div(id = Year_div,selectInput(Year,"Year_App",c(2020:2050)))        
                     )
               })
            })

     observe({
               n <- 3
               list <- as.list(1:n)
               lapply(list, function(i){
                  panels <- paste0("panels",i)
                  Year_App <- reactive(input[[paste0("Year_App",i)]]) # Year input wrapped in reactive function
                          callModule(server_module,panels,Year_App)
                    })
               })
    }

shinyApp(ui,server)

As you run the App you will see that the default values specified in the Module ui doesn't appear at the start of the app and the update values appear as if I am using observe instead of an observeEvent even after wrapping the year input inside reactive function and using ignoreInit = T

Thanks and Regards
Jitu

Step 1: clean up your reprex so it's a bit more minimal. You included a lot of styling that makes it hard to see that shape of the app.

library(shiny)

ui_module <- function(id) {
  ns <- NS(id)
  list(
    selectInput(ns("Year"), "Year_Module", 2020:2050),
    numericInput(ns("default1"), "Deafault1", NA, value = 26542, step = 1),
    numericInput(ns("default2"), "Deafault2", NA, value = 89609, step = 1)
  )
}

server_module <- function(input, output, session, Year_App) {
  Year_Mod <- reactive(input$Year) 
  observeEvent(Year_App(), {
    updateNumericInput(session, "default1", value = 80)
    updateNumericInput(session, "default2", value = 40)
  })
}

ui <- fluidPage(
  uiOutput("ui")
)

server <- function(input, output, session) {
  output$ui <- renderUI({
    lapply(1:3, function(i) {
      wellPanel(
        ui_module(paste0("panels", i)),
        selectInput(paste0("Year_App", i), "Year_App", c(2020:2050))
      )
    })
  })

  observe({
    lapply(1:3, function(i) {
      panels <- paste0("panels", i)
      Year_App <- reactive(input[[paste0("Year_App", i)]]) # Year input wrapped in reactive function
      callModule(server_module, panels, Year_App)
    })
  })
}

shinyApp(ui, server)

Step 2: rewrite using modern module style (https://mastering-shiny.org/scaling-modules.html) and add a check that the input to the module server is reactive.

myModuleUi <- function(id) {
  list(
    selectInput(NS(id, "Year"), "Year_Module", 2020:2050),
    numericInput(NS(id, "default1"), "Deafault1", NA, value = 26542, step = 1),
    numericInput(NS(id, "default2"), "Deafault2", NA, value = 89609, step = 1)
  )
}
myModuleServer <- function(id, Year_App) {
  stopifnot(is.reactive(Year_App))
  
  moduleServer(id, function(input, output, session) {
    observeEvent(Year_App(), {
      updateNumericInput(session, "default1", value = 80)
      updateNumericInput(session, "default2", value = 40)
    })
  })
}

Step 3: update UI and server to use new module, remove redundant observe(). It now works. I'm not sure why, but you possibly because of the observe() that wasn't doing anything.

library(shiny)

# Use modern module style
moduleServer <- function(id, module) {
  callModule(module, id)
}

# Module ------------------------------------------------------------------
myModuleUi <- function(id) {
  list(
    selectInput(NS(id, "Year"), "Year_Module", 2020:2050),
    numericInput(NS(id, "default1"), "Deafault1", NA, value = 26542, step = 1),
    numericInput(NS(id, "default2"), "Deafault2", NA, value = 89609, step = 1)
  )
}
myModuleServer <- function(id, Year_App) {
  stopifnot(is.reactive(Year_App))
  
  moduleServer(id, function(input, output, session) {
    observeEvent(Year_App(), {
      updateNumericInput(session, "default1", value = 80)
      updateNumericInput(session, "default2", value = 40)
    })
  })
}

# Main app ----------------------------------------------------------------

ui <- fluidPage(
  uiOutput("ui")
)

server <- function(input, output, session) {
  output$ui <- renderUI({
    lapply(1:3, function(i) {
      wellPanel(
        myModuleUi(paste0("panels", i)),
        selectInput(paste0("Year_App", i), "Year_App", c(2020:2050))
      )
    })
  })

  lapply(1:3, function(i) {
    Year_App <- reactive(input[[paste0("Year_App", i)]])
    myModuleServer(paste0("panels", i), Year_App)
  })
}

shinyApp(ui, server)

First of all thanks a lot for the valuable suggestions while writing shiny modules neat and clean and this would definitely help me to write codes better.

It is still not working as the values (26542,89609) specified in the numericInputs are still not the default values. The expHndlr is still giving me the updated values(80,40) specified in the updateNumericInput in the module server.

Here is a very simple code that worked for me but your solution is still not doing the same thing. You can notice that at start of the app the number is 26542 but as we change year the number changes to its updated value 7. Same thing if we manually change the number, only changing the year from its current value does the updation.

 library(shiny)

   ui <- fluidPage(
              wellPanel(
                    selectInput("Year", "Year", 2020:2050, width = "50%"),
                    numericInput("default1", "Deafault1", NA, value = 26542, step = 1, width = "35%")
           )
         )

   server <- function(input,output,session){

        observeEvent(input$Year,{
                updateNumericInput(session,
                             "default1",value = 7, step = 1)
                        },ignoreInit = TRUE)
            }

  shinyApp(ui,server)

ignoreInit = TRUE is not working in your solution as well. The values showing are only updated values.

Thanks and Regards

Jitu

Ok, now I understand what your problem actually is. I'll take another look tomorrow.

Here's a minimal reprex without the module. It works as expected — the value of x is only reset after you modify y:

ui <- fluidPage(
  numericInput("x", "x", value = 9999, step = 1),
  selectInput("y", "y", c(2020:2050))
)
server <- function(input, output, session) {
  y <- reactive(input$y)
  
  observeEvent(y(), {
    updateNumericInput(session, "x", value = 0)
  }, ignoreInit = TRUE)
}

shinyApp(ui, server)

So what's different with that app and the module that fails? The use of dynamic UI and a module. What happens if I use renderUI():

ui <- fluidPage(
  uiOutput("ui")
)
server <- function(input, output, session) {
  output$ui <- renderUI({
    list(
      numericInput("x", "x", value = 9999, step = 1),
      selectInput("y", "y", c(2020:2050))
    )
  })
  
  y <- reactive(input$y)
  
  observeEvent(y(), {
    updateNumericInput(session, "x", value = 0)
  }, ignoreInit = TRUE)
}

shinyApp(ui, server)

It again fails — and that makes me realise why. The initial value of input$y is going to be NULL, because when the app loads there is no y input, so it gets a default value ofy. So we need some way to make sure that the first time that the observeEvent() is called is after renderUI() has generated the call. I think the easiest way to do that is to use req():

ui <- fluidPage(
  uiOutput("ui")
)
server <- function(input, output, session) {
  output$ui <- renderUI({
    list(
      numericInput("x", "x", value = 9999, step = 1),
      selectInput("y", "y", c(2020:2050))
    )
  })
  
  y <- reactive({
    req(input$y)
    input$y
  })
  
  observeEvent(y(), {
    updateNumericInput(session, "x", value = 0)
  }, ignoreInit = TRUE)
}

shinyApp(ui, server)
1 Like

Thanks a lot for providing me with this explanation. I was certainly asking the wrong question while debugging my app. It is surely working now.

Regards

Jitu

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.