In my Shiny applications, I often encounter the need to keep a reactive object (e.g., a table) in sync with an external data source over time, while also updating it immediately after a user-triggered change.
A typical example is a database table: I want to display an up-to-date version of the data to the user. For this purpose, Shiny already provides reactivePoll(), which allows periodic checks for updates and refreshes the reactive accordingly.
However, in most real-world scenarios where I use reactivePoll(), I also need a mechanism to trigger an immediate refresh after a user modifies the underlying data. For instance, if a user inserts a new record into the database, they naturally expect to see it reflected in the UI right away.
One possible approach is to set a very short polling interval, but this is often not feasible. In practice, multiple users may be connected to the same worker, and frequent polling can lead to excessive database queries and performance degradation (I have personally experienced this causing the app to freeze). For this reason, I typically use a relatively long polling interval (e.g., ~100 seconds), which balances freshness and performance.
The downside is that, with this setup, a user may need to wait over a minute to see changes they just made, resulting in a poor user experience and a perception that the app is unresponsive.
To address this, I have repeatedly implemented workarounds that combine:
- periodic polling (at a reasonable interval), and
- a manual trigger to refresh the reactive immediately after a user update.
Conceptually, what I need is a refreshable reactivePoll(): a version that can be invalidated programmatically, without waiting for the next polling cycle.
As a proof of concept, I implemented a small extension of reactivePoll() that returns a list instead of a single reactive:
value: the reactive expression (same as standard reactivePoll())
refresh(): a function that forces a refresh by updating the internal “cookie” used for change detection
Here is the implementation:
refreshableReactivePoll <- function(
intervalMillis,
session,
checkFunc,
valueFunc
) {
intervalMillis <- shiny:::coerceToFunc(intervalMillis)
rv <- reactiveValues(cookie = isolate(checkFunc()))
re_finalized <- FALSE
env <- environment()
o <- observe({
if (re_finalized) {
o$destroy()
rm(o, envir = env)
return()
}
rv$cookie <- checkFunc()
invalidateLater(intervalMillis(), session)
})
re <- reactive(
{
rv$cookie
valueFunc()
},
label = NULL
)
reg.finalizer(attr(re, "observable"), function(e) {
re_finalized <<- TRUE
})
re <- list(
value = re,
refresh = function() {
rv$cookie <- checkFunc()
}
)
on.exit(rm(re))
return(re)
}
Updating the “cookie” rather than the reactive value directly avoids redundant updates when the next polling cycle runs, since the state is already aligned. This allows the reactive to be refreshed both periodically and on demand (e.g., immediately after a database write).
I am not aware of an officially recommended pattern for this use case, but this need arises frequently in my applications. It feels like Shiny currently lacks a clean, built-in way to handle this pattern efficiently and systematically.
I believe that adding native support for a programmatically refreshable reactivePoll() (or a similar abstraction) would be a valuable enhancement for many real-world applications.
In my Shiny applications, I often encounter the need to keep a reactive object (e.g., a table) in sync with an external data source over time, while also updating it immediately after a user-triggered change.
A typical example is a database table: I want to display an up-to-date version of the data to the user. For this purpose, Shiny already provides reactivePoll(), which allows periodic checks for updates and refreshes the reactive accordingly.
However, in most real-world scenarios where I use reactivePoll(), I also need a mechanism to trigger an immediate refresh after a user modifies the underlying data. For instance, if a user inserts a new record into the database, they naturally expect to see it reflected in the UI right away.
One possible approach is to set a very short polling interval, but this is often not feasible. In practice, multiple users may be connected to the same worker, and frequent polling can lead to excessive database queries and performance degradation (I have personally experienced this causing the app to freeze). For this reason, I typically use a relatively long polling interval (e.g., ~100 seconds), which balances freshness and performance.
The downside is that, with this setup, a user may need to wait over a minute to see changes they just made, resulting in a poor user experience and a perception that the app is unresponsive.
To address this, I have repeatedly implemented workarounds that combine:
Conceptually, what I need is a refreshable reactivePoll(): a version that can be invalidated programmatically, without waiting for the next polling cycle.
As a proof of concept, I implemented a small extension of reactivePoll() that returns a list instead of a single reactive:
value: the reactive expression (same as standard reactivePoll())refresh(): a function that forces a refresh by updating the internal “cookie” used for change detectionHere is the implementation:
Updating the “cookie” rather than the reactive value directly avoids redundant updates when the next polling cycle runs, since the state is already aligned. This allows the reactive to be refreshed both periodically and on demand (e.g., immediately after a database write).
I am not aware of an officially recommended pattern for this use case, but this need arises frequently in my applications. It feels like Shiny currently lacks a clean, built-in way to handle this pattern efficiently and systematically.
I believe that adding native support for a programmatically refreshable reactivePoll() (or a similar abstraction) would be a valuable enhancement for many real-world applications.