Skip to contents

Filtering by Id is very experimental and may fail in not yet fully understandable way.

Motivation

First (0.0.1) version of shinybreakpoint treated code as a words segregated into R files, but only in some specific situations source code can be read in the same way as a book - e.g. any function definition makes it necessary to jump from one line to another to find the body of function. Reactive programming makes it even necessary to jump all the time between blocks of reactive context (which may exist in different files) - relevant code often is far away from where we are, between the code blocks connected to the outputs we do not looking for. This is of course the more problematic the less we know (or remember) code structure of the app.

The problem with the complicated app structure (complicated app itself and fact that it was developed using reactive programming) found the answer in reactlog package, where it is possible to see the graph with connected inputs, outputs and other reactive elements (reactives, observes) and where is possible to retrieve this information. shinybreakpoint base on this to provide possibility to display source code (but only reactive context) which is relevant for (connected to) given Id, where Id is an input or output Id.

Filtering

To the right of red filled circle button (), the set of three small buttons is displayed and only the first one () is active all the time - this is a button responsible to display source code in a standard way (as a files). Other two are active if some Id is present to use it for filtering.

Last changed input

The second button () can be use immediately after some input in the app has been changed1 and this input is connected to any reactive context. input can change due to user action, but the change can also be a result of some code running in the server part (like usage of update* function in observe). This mode can search (and it does this automatically) relevant source code - what may be already obvious - only for input Id.

Chosen Id

Finally, third button () is responsible to display source code relevant for previously chosen Id. Choosing is performed by holding Ctrl on PC or Cmd on Mac and hovering over input or output element - in this case, similar to last changed input, Id will be saved if is connected to any reactive context. Visually, the indicator of this is a displayed cursor of type progress for a moment.

This mode makes it possible to choose input or output element.

Requirements

Filtering mode can’t display more lines of source code than standard mode, but it can display less lines of code than in reality belongs to the Id. As a reminder and the first point, it should be noted that body of reactive context must be (and that’s also requirement for the standard mode) inside curly ({}) brackets to be found by shinybreakpoint as well as the body should be in separate line (so there is a space to set breakpoint), e.g.

r_iris <- reactive({
  iris
})

And additionaly (and also as a reminder as this was described in the article App structure), reactive context must be inside server part of the main app or server part of module (in theory - i.e. shiny allows for this - in can be outside of the server and UI, but shinybreakpoint won’t find these code blocks).

Thinking about other requirements specific just for filtering by Id, it must be said that along with limitations linked to shinybreakpoint, some of them are the consequence of using reactlog / shiny::bindEvent(). In the latter we can spot situations where source reference to object is keep and situations where is not - the second situation is of course problematic, because we don’t have an easy access to the name of the file and line where the reactive context exists. Source reference (srcref attribute) is not keep if:

  • curly brackets are not used in the body of reactive context
  • observe() (or observeEvent()) is used, no matter if the code block is in curly brackets ({}) or not
  • shiny::bindEvent() is used, no matter if on observe, reactive or render* object2

Luckily, we can use label parameter to answer these inconveniences. label is a parameter which exists for observe() (and observeEvent()), reactive() (and eventReactive()) and bindEvent(). Now - shinybreakpoint reads the source code to retrieve these labels along with the information in which line and file, label was found. However, this process is successful only if:

  • argument passed to label parameter is just a string (i.e. not a variable, not a string inside function call etc.)
  • argument passed to label parameter is unique across all labels and Ids (i.e. think about the label as an Id which must be unique)
  • but, at the same time, if bindEvent() is used on the render* function, then label must be the same as Id3.

Examples:

library(shiny)
library(magrittr)

server <- function(input, output, session) {
  iris_r <- reactive({
    iris
  }) %>% 
    bindEvent(label = "iris data")
  
  show_table <- renderTable({
    iris_r()
  }) %>% 
    bindEvent(label = "show_table")
  
  
  cars_r <- reactive({
    cars
  })
  
  observe({
    invalidateLater(10000)
    saveRDS(cars_r(), "file_saved_every_10_sec.rds")
  }, label = "save cars dataset")
  
}

To summarize: if observe(), observeEvent() or bindEvent() is used (even on reactive()), then string must be passed directly to the label parameter and this string can’t be the same as other labels or Ids (across the whole app!) except if bindEvent() is used on render* object, because then a string passed as an argument to label parameter must be the same as the output Id.

Please keep in mind that one of the main ideas on which modules in shiny are build is to construct separate namespace inside the app, so one should remember about this when trying to apply above rules for bindEvent() used on render* function:

# let's say `id` will be 'my_mod'
irisUI <- function(id) {
  ns <- NS(id)
  tagList(
    actionButton(ns("show"), "Show iris")
    tableOutput(ns("iris"))
  )
}

irisServer <- function(id) {
  moduleServer(
    id,
    function(input, output, session) {
      iris <- renderTable({
        iris
      }) %>% 
        bindEvent(input$show, label = "my_mod-iris")
    }
  )
}

We can see that to the Id (iris) it was necessary to add my_mod-, this is because NS() returns a function which will add prefix to the Id and this prefix is an id with - added: NS() is a function factory (makes other functions); we are passing a specific argument to NS() and then binds this to the ns name, making ns() function - ns() function returns the id which was passed to the NS(), then - sign and then Id passed to the ns() function - everything pasted together.

Full app example would be like that:

library(shiny)
library(magrittr)

shinybreakpoint::set_filtering_by_id() # TODO: remove

ui <- fluidPage(
  theme = bslib::bs_theme(5),
  irisUI("my_mod") # module UI used
)

appServer <- function(input, output, session) {
      
}

server <- function(input, output, session) {
  appServer(input, output, session)
  irisServer("my_mod") # module server used
  shinybreakpoint::shinybreakpointServer() # TODO: remove
}

    shinyApp(ui, server)
irisUI <- function(id) {
  ns <- NS(id)
  tagList(
    actionButton(ns("show"), "Show iris")
    tableOutput(ns("iris"))
  )
}

irisServer <- function(id) {
  moduleServer(
    id,
    function(input, output, session) {
      iris <- renderTable({
        iris
      }) %>% 
        bindEvent(input$show, label = "my_mod-iris", ignoreInit = TRUE)
    }
  )
}

Keeping reactlog clean

shinybreakpoint::set_filtering_by_id() enables reactlog, but also when the app stops, removes reactlog data from temporary directory. This is a negative result of manipulating functions attributes by shinybreakpoint - reactlog data can’t be just recreate when the app is running, but only when the app starts, otherwise Id dependency won’t be find correctly.

This has two consequences - first one is that if one want to change something in the source code of the app, then the app has to be stopped and run again - however it shouldn’t be a big inconvenience since this is also necessary to do if source code in module was modified and shinybreakpoint was created having modules in mind.

The second consequence is that for some reason when the app is stopped during debug mode, i.e. Q was used in debug mode, then reactlog data won’t be cleaned properly. Unfortunately, it means that to use filtering by Id, Q option in debug mode can’t be use - it one would like to exit from the debug mode and app, it is only possible to return to app (by f or c in debug mode) and then stopping the app.