The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Working with collect() promises in reactive contexts

jsplyr keeps your data in the browser, so collect() has to fetch it back over an asynchronous round-trip. Because of that, collect() returns a promise, not a data frame.

Reactive outputs such as shiny::renderTable() or DT::renderDT() understand promises and resolve them for you, which is why a plain collect() inside a render function “just works”:

output$mtcars_tb <- DT::renderDT({
  lazy_mtcars_query() |>
    dplyr::collect()
})

Every other reactive context — reactive(), eventReactive(), observeEvent() and observe() — hands you the promise as-is. The value resolves later, so you cannot use the result of collect() synchronously on the next line; treating it as a data frame directly gives you a promise object instead of your rows. You handle it with promises::then() or the re-exported %...>% pipe.

Two ways to unwrap a promise

Both of these work in any context — reactive(), eventReactive(), observeEvent() and observe() alike. Pick whichever reads better. The examples below use the %...>% pipe.

promises::then()

then() registers a callback that runs once the data arrives. It returns a new promise, so it composes:

lazy_mtcars_query() |>
  dplyr::collect() |>
  promises::then(function(df) {
    # `df` is the collected tibble
    head(df)
  })

%...>% pipe

The promises package ships a “promise pipe”, %...>%, that pipes the resolved value into the next expression. It reads just like a regular pipe but waits for the promise to settle first. jsplyr re-exports it, so you do not have to import promises yourself:

lazy_mtcars_query() |>
  dplyr::collect() %...>%
  head()

Use %...!% to handle errors from the promise chain:

lazy_mtcars_query() |>
  dplyr::collect() %...>%
  head() %...!%
  (function(err) {
    shiny::showNotification(conditionMessage(err), type = "error")
  })

A practical example: updating an input from a computed value

A common task is to set the value of an input — say a selectInput — from a number computed in the browser. Functions like shiny::updateSelectInput() are side effects, not reactive outputs. They do not understand promises, so you cannot pass collect()’s result straight to selected — you would hand it a promise object instead of your value. Resolve the promise first and act on the value once it arrives.

The cleanest approach keeps the updateSelectInput() call outside the jsplyr pipeline. Store the resolved value in a reactiveVal and let a separate observer update the input. This decouples “compute the value” from “update the input”:

# Holds the value computed in the browser.
oldest_age <- shiny::reactiveVal(NULL)

# Pipeline: compute max(age) and store it. No UI update here.
shiny::observeEvent(input$update, {
  lazy_data() |>
    dplyr::summarise("max_age = max(age)") |>
    dplyr::collect() %...>% {
    oldest_age(.$max_age)
  }
})

# Separate observer: update the input when the computed value changes.
shiny::observeEvent(oldest_age(), {
  shiny::updateSelectInput(
    session,
    inputId = "age",
    selected = oldest_age()
  )
})

The %...>% pipe waits for the collected value to arrive, then . holds the result tibble so .$max_age is written into the reactiveVal. The second observer reacts to that change and performs the update.

If you prefer to keep the computation in a reactive expression instead of an observer, return the collected promise from an eventReactive() (gated on the button) and resolve it in a separate observe():

# Gated on the button: returns the collect() promise. Computes only.
oldest_age <- shiny::eventReactive(input$update, {
  lazy_data() |>
    dplyr::summarise("max_age = max(age)") |>
    dplyr::collect()
})

# Separate observer: resolve the promise and update the input.
shiny::observe({
  oldest_age() %...>% {
    shiny::updateSelectInput(
      session,
      inputId = "age",
      selected = .$max_age
    )
  }
})

eventReactive() returns the promise as-is, so oldest_age() is a promise; the observe() resolves it with %...>% and updates the input. A plain reactive() works the same way if you want the value recomputed whenever its dependencies change rather than only on a button press.

See inst/example_apps/app_update_select.R for a complete runnable app showing both approaches.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.