Search code examples
rshinyobserversr-plotly

Unwanted popup storm triggered by old observeEvents created inside a function


How to stop observers created within a function from existing more than once when data changes?

UPDATE: - Further simplified the app on github for easier testing. - been trying to use destroy ----

I have made an app that requires several (10ish) versions of a plot throughout my program, and therefore I have written 2 functions:

  • myplotly which creates the plots
  • mypopup which creates the popups containing a pulseshape plot belonging to each particle in the myplotly plots, plus option buttons in the modaldialog popup, and the belonging observers

These dialogues are opened when the user clicks on a point in the first graph.

the shortened version of this function looks like this:

mypopup <- function(THEDATAFRAME, THEPLOT, THEGROUP, THEPULSEFRAME) {
..... bla bla lots more code in real app.....
  ..... one example of an observer with only 1 of the arguments needed for this one
  observeEvent(input[[paste("Close", THEPLOT, sep ='.')]], {
    removeModal()
  }, ignoreInit = T)

}

pic

The functions *1 are called when the corresponding data frames are created or loaded and are called again when the data changes.

The problem I am facing is that after the data has been changed and the user clicks on a point the popup and all observers fire twice. If the data is changed again the pop-ups fire 3 times and so forth. As well as that the popups based on the old data don't understand that there are now more than 1 cluster in the plot, so if you click cluster 2 or 3, error/ NA plots etc..

enter image description here

Statements:

  • using obs$destroy might solve things, but not easy (see footnote *2 )
  • why do we get double observers in the first place? Fixing the symptoms is one thing, understanding why they are not simply overwritten is still a mystery to me.

I have created a stripped down example of the working code plus some files to run it on all of which can be found on the following github link: https://github.com/madmark81/Observer-Madness

footnotes

1 The reason why I have written it as functions it's because I apply this combination of plots and pop-ups several times in my main app each time depending on a unique combination of a base dataframe and a pulse dataframe as well as columns containing group ID and group name.

2 Maximilian came up with the idea to use assigning observers to a variable. But implementation of this has to be done outside the mypopup function it seems and I haven't been able to get it to work on observers that use more than 1 input argument, which normally is sorted out by the 4 input arguments of the mypopup function, but can't be done using an lapply call as is done for the following example that only happens to have 1 input argument:

  lapply(plot.list, function(x){  
    o <- observeEvent(event_data("plotly_click", source = paste("plotlyplot", x, sep = '.')), {
      print('clicked')
      if(values[[paste("particle_viewer", x, sep = "_")]]) {
        ## when click in plot: Highlight the clicked particle with java, but also store the clicked point
        values[[paste("HLval", x, sep = "_")]] <- event_data("plotly_click", source = paste("plotlyplot", x, sep = '.'))  ## this code stores the last clicked point so that the point stays active when object is re-rendered
      }
    })
  })

followed by a destroy call every time the data is changed.

  observeEvent( values$TrainDFLogged,   {
    lapply(c("o"), function(x) {
          if (exists(x))
          {get(x)$destroy
            print('destroyed2')
          }
        })
  mypopup(values$TrainDFLogged, "SecondFile", "default", values$TrainPulses)
  })

UPDATE 2

When the observer is created OUTSIDE of the mypopup function code however, there is no need for the destroy and assigning to 'o' it seems, clicks are only observed on time I just found out. So perhaps we should simply find a way to create all the observers in an lapply or mapply style solution, but outside the mypopup code ?


Solution

  • The cause was copies of the same observeEvent being created in a dynamic approach (lapply over x length)

    The issue is that Shiny overwrite ui elements when they are rebuild (or at least only displays the newest version of it, but when you build numbered observeEvents to listen to a set of numbered buttons or such, it creates copies of each of those observers if you simply build them from i.e. 1:10 in a lapply.

    the solution to prevent this problem is, is to record how many observers you made so far (max nr looped over), and then when the code associated with the loop updates, and requires a larger nr of observers, only make new ones for the nrs higher than the previous max.

    in other words loop over 'previous maximum'+1 (which is 0 the first time) till new maximum, making sure there is only