0

Iā€˜m trying to develop an Rshinyapp that requires clicking a button to perform tasks such as data processing and plotting. Upon clicking the button, the UI will display another button to analyze data and save images.

Due to this logical relationship, I have implemented nested observeEvent to achieve the desired functionality. However, the issue now is that every time the second button is clicked, the inner observeEvent not only runs once, but also runs a number of times equal to the cumulative number of times the first button has been clicked. This redundancy in computation is negatively impacting the efficiency of the software. How can I solve this problem?

In the following simple example, I have simplified and reproduced the problem I encountered. You can try running this code and clicking the first and second buttons multiple times to observe the issue I mentioned.

library(shiny)

ui <- fluidPage(
  actionButton("b1", "button 1"),
  actionButton("b2", "button 2"),
)

server <- function(input, output, session) {
  x <- 1
  cat(sprintf("out x = %d \n", x))
  cat("sleep: 1s \n")
  Sys.sleep(1)  
  
  observeEvent(input$b1,{
    # Every time you click button 1, the code below will run once.
    x <<- x+1
    cat(sprintf("ob1 x = %d \n", x))
    cat("sleep: 1s \n")
    Sys.sleep(1)                           
    
    observeEvent(input$b2, {
      # Clicking button 2 once will trigger the code below to run multiple times,
      #      equal to the cumulative number of times you have clicked button 1.
      x <<- x+1
      cat(sprintf("ob2 x = %d \n", x))
      cat("sleep: 1s \n")
      Sys.sleep(1)}) 
  })
 
}

shinyApp(ui, server)

I think this nested structure is irreplaceable because the second button only displays when the correct data is inputted, and for ease of operation, the inputted data is saved in an observeEvent for use or modification. I have tried to replace observeEvent with eventReactive, or add the ignoreInit = TRUE parameter, but these attempts did not work.

There are some similar issues, but it seems they don't solve my problem. You can use them as a reference: ref.1, ref.2

Thanks in advance to anyone who tries to help me.

LIANG Chen
  • 35
  • 6
  • 1
    Nesting `reactive`s is a fundamental error. You need to rethink your program structure. One way to do that would be to set a `reactiveVal` when the first button is clicked. Use a `uiOutput`/`renderUI` pair to display the second button when the `reactiveVal` first changes. React to a click of the second button in whatever way you need. PS "the inputted data is saved in an observeEvent" makes no sense. (And current recommended practice is to use `bindEvent` rather than `eventReactive`. See the online doc for more information.) – Limey Mar 23 '23 at 17:25

2 Answers2

0

Expanding on my comment, does this give you the behaviour you seek?

library(shiny)

ui <- fluidPage(
  actionButton("b1", "Button 1"),
  uiOutput("dynamicUI")
)

server <- function(input, output) {
  rv <- reactiveValues(button1Clicked=FALSE, x=0, y=0)

  observeEvent(input$b1, {
    rv$button1Clicked <- TRUE
    rv$x <- rv$x + 1
  })

  output$dynamicUI <- renderUI({
    if (rv$button1Clicked) {
      actionButton("b2", "Button 2")
    }
  })

  observeEvent(input$b2, {
    rv$y <- rv$y + 1
    print("Button 2 clicked")
    print(paste0("The value of x is ", rv$x))
    print(paste0("The value of y is ", rv$y))
  })
}

shinyApp(ui, server)
Limey
  • 10,234
  • 2
  • 12
  • 32
  • Thank you for your answer. Your suggestion is helpful. However, my shinyapp is quite complex, and I still need to carefully consider whether to abandon nested structures. Using the `reactiveVal` may be a possible alternative, and I will try to use it. Thanks again. – LIANG Chen Mar 24 '23 at 07:51
  • 1
    I'm happy to have helped. But I repeat my warning that nested `reactive`s are a recipe for disaster. You may have solved this particular problem by other means, but you have only delayed the inevitable. Bite the bullet. Unnest your `reactive`s. A little pain now will avoid agony in the future. – Limey Mar 24 '23 at 08:50
0

Inspired by ref.2, I made modifications to my own code. This modification solved the issue I mentioned above.

In the modified version, I added a variable to record the number of executions and used it to identify and skip the repeated running parts. The detailed code is as follows.

However, as @Limey pointed out, nested structures may need to be avoided, and my code is for reference only.

library(shiny)

ui <- fluidPage(
  actionButton("b1", "button 1"),
  actionButton("b2", "button 2"),
)

server <- function(input, output, session) {
  cat("begin \n")
  b1.times <- reactiveVal(1)
  b2.times <- reactiveVal(1)
  
  observeEvent(input$b1,{
    # section b1
    cat(sprintf("b1 value: %d \n", input$b1))
    cat(sprintf("b1 times: %d \n", b1.times()))
    cat("sleep: 1s \n")
    Sys.sleep(1)
    cat("--------------------------------------\n")
    
    observeEvent(input$b2, {
      # section b2
      # If the number of times that section b2 has been run does not match the value of input$b2, 
      #     then exit this section.
      if(b2.times() != input$b2) return()
      
      cat(sprintf("b1 value: %d \n", input$b1))
      cat(sprintf("b1 times: %d \n", b1.times()-1))
      cat(sprintf("b2 value: %d \n", input$b2))
      cat(sprintf("b2 times: %d \n", b2.times()))
      cat("sleep: 1s \n")
      Sys.sleep(1)
      cat("--------------------------------------\n")
      
      b2.times(b2.times()+1)  #record the number of times that section b2 has been executed.
    })
    b1.times(b1.times()+1)    #record the number of times that section b1 has been executed.
  })
}
shinyApp(ui, server)
LIANG Chen
  • 35
  • 6