A reactives object is a keyed collection of shiny reactives, such as
shiny::reactiveVal() and shiny::reactive() objects. Slots can be added,
removed and reordered, and each slot is tracked as its own reactive
dependency. The reactive_vals() constructor creates a collection whose
slots are shiny::reactiveVal() objects holding the given values, and which
only accepts shiny::reactiveVal() slots, so code writing through its slots
can rely on them being writable. Its counterpart, reactive_exprs(),
creates a collection that only accepts reactive expressions, the reactives
of class reactiveExpr that shiny::reactive(), shiny::eventReactive(),
shiny::debounce() and the like return, so that none of its slots can be
written. To test whether an object is a collection of any kind, use
is_reactives().
Usage
reactives(...)
reactive_vals(...)
reactive_exprs(...)
# S3 method for class 'reactives'
reorder(x, order, ...)
append_reactive(x, value)
append_value(x, value)
is_reactives(x)
as_values(x)
has_key(x, key)
snapshot_reactives(x)Arguments
- ...
For
reactives(),reactive_vals()andreactive_exprs(), the slots to hold, named or unnamed: reactives forreactives()and reactive expressions forreactive_exprs(), withNULLentries dropped, and any values, includingNULL, forreactive_vals(). Ignored byreorder().- x
A
reactivesobject, or foris_reactives(), any object.- order
The new order, listing every slot exactly once: by position, or by name when all slots are named.
- value
For
append_reactive(), the reactive to bind to the new slot, and forappend_value(), the value for the new slot'sshiny::reactiveVal()to hold.- key
The names of the slots to test for, none of them empty or missing.
Value
A reactives object, or for reactive_vals() and reactive_exprs()
an object of that class, which is also a reactives object. A snapshot
has the class of x. The reorder() method returns x, invisibly, and so
do append_reactive() and append_value(). The as_values() function
returns a list of the slots' values, in slot order and named as by
as.list(), has_key() returns a logical vector with one element per
name in key, and is_reactives() returns TRUE or FALSE.
Details
A collection has two levels, as a list does: the slots, and the values
their reactives return. Reading with $ or [[, as in x$a, x[["a"]] or
x[[1]], returns a slot's value, as it does for a shiny::reactiveValues()
object, while reading with [, as in x["a"] or x[1], returns the slot's
reactive. As on a list, a name with no slot reads as NULL, while a
position past the last slot is an error. A slot whose shiny::reactiveVal()
stores NULL reads as NULL with $ and [[ too, but not with [, which
tells it apart from a missing slot. To test for slots by name, use
has_key().
Each read takes a single name or position. Take the reactives of several
slots from as.list(x)[i] instead, or get the value of every slot at once
with as_values(), as shiny::reactiveValuesToList() does for a
shiny::reactiveValues() object. Going through the slots' reactives works
with lapply() and vapply(), which call as.list(), while Map() reads
the slots by position with [[ and so goes through their values. A for
loop, do.call() and purrr's map() functions don't dispatch on the class,
though, so they see the collection's internal fields instead of its slots.
With those, go through as.list(x) instead, as in
for (slot in as.list(x)).
Assigning a reactive with [<-, as in x["a"] <- r, binds it to the slot,
adding the slot or replacing its reactive, and assigning NULL with [<-
removes the slot, as for a list. Assigning anything else with [<- is an
error. Assigning with $<- or [[<- writes a value instead, as for a
shiny::reactiveValues() object: through the slot's shiny::reactiveVal(),
which stays bound, or where there is no slot, by binding a new
shiny::reactiveVal() that holds the value. Any value is stored as it is, a
reactive included, and unlike on a list, assigning NULL with $<- or
[[<- stores NULL rather than removing the slot. A slot holding a
reactive expression, such as a shiny::reactive(), can't be written, so
that a stray value can't overwrite it; replacing its reactive takes [<-.
A reactive_exprs collection only holds such slots, and assigning a value
to it with $<- or [[<- is an error even where there is no slot, so that
a stray value can't add one either. As with reading, each assignment takes a
single name or position.
Slots can be unnamed. To append one, bind a reactive r with
append_reactive(x, r), or write a value with append_value(x, value),
which binds a new shiny::reactiveVal() holding it. The checks are those of
binding and writing, so append_value() is an error for a reactive_exprs
collection.
Renaming with names<- is an error, as it is for a
shiny::reactiveValues() object. To rename a slot, bind its reactive under
the new name, remove the old slot and restore the order with reorder().
Sharing
A collection is shared, like shiny::reactiveValues(): after y <- x, both
names refer to the same collection, and a change made through [<-, $<-,
[[<-, reorder(), append_reactive() or append_value() is seen by
everything holding it. That is what lets one part of an app change a
collection while another reacts to it.
Lifetime
A collection belongs to the session or module it was created in and is
destroyed along with it, as a shiny::reactiveVal() is. Reading or changing
it from another module doesn't tie it to that module, so destroying the
module leaves the collection intact. A reactive bound to a slot, however,
still belongs to the module it was created in, while the
shiny::reactiveVal() that $<-, [[<- or append_value() creates for a
new slot belongs to the collection, whichever module writes the value.
To keep reading a collection after its shiny::testServer() session has
closed, a test can take a snapshot with snapshot_reactives() while the
session is still open. A snapshot belongs to no session. For each
shiny::reactiveVal() slot it holds a new shiny::reactiveVal() with the
slot's current value, and for each other slot a shiny::reactive()
returning its current value, so writing through a slot of the snapshot or
of the original leaves the other unchanged. A slot that fails when computed
fails the same way when its copy is called, rather than failing the
snapshot. Names, order and class carry over.
Dependencies
Reading a slot's reactive by name, as in x["a"], makes the caller depend
on that slot alone. The caller re-runs when the slot is bound, replaced or
removed, but not when other slots change. This also holds for a slot that
does not exist yet, so a reader re-runs once the slot is added. Reading the
slot's value, as in x$a or x[["a"]], depends on the slot and on its
value, as calling its reactive does, so the caller also re-runs when the
value changes. Testing for slots with has_key() has the same
dependencies as reading their reactives by name, while testing with
"a" %in% names(x) depends on what names() depends on, so the caller
re-runs whenever a slot is added, removed or moved.
The length() method depends on the number of slots alone, and names() on
which slots exist and their order. Reading a slot by position, as in x[1]
or x[[1]], depends on what names() depends on and on the slot it finds,
and x[[1]] also on its value, so the caller re-runs whenever a slot is
added, removed or moved, even if the slot at its position stays the same.
Reading past the last slot fails, and the caller re-runs on the same
changes. The as.list() method depends on what names() depends on and on
every slot, and as_values() also on every slot's value. Reordering
re-runs readers of names(), as.list(), as_values() and of slots by
position, but not readers of length() or of slots by name.
To depend on the slot at a position alone, read it through a
shiny::reactiveVal(). After first <- reactiveVal() and
observe(first(if (length(x)) x[1])), a reader of first() re-runs only
when position 1 comes to hold a different reactive or none, since writing a
shiny::reactiveVal() the value it already holds invalidates nothing. The
check on length() keeps the observer from failing on an empty collection,
which in an app would end the session.
Binding, removing and writing make the caller depend on nothing, and so does
appending with append_reactive() or append_value(). Writing a value
through a slot's shiny::reactiveVal() leaves the slot bound to it, so the
write re-runs only the readers of the slot's value, and only if the value
changes. Writing a value where there is no slot binds a new one and re-runs
readers as any binding does.
Assigning one past the last slot, as in x[length(x) + 1] <- r or
x[[length(x) + 1]] <- value, appends as well, but the call to length()
makes the caller depend on the number of slots, which the append then
changes. An observer that appends this way re-runs after each append and
appends again, without end.
Printing, format() and str() make the caller depend on nothing, and they
never call a slot, so they run no computed slot. Taking a snapshot also
makes the caller depend on nothing, though it runs every computed slot.
Outside a reactive consumer, as at the console, names() and length()
work as they would inside shiny::isolate() rather than fail.
Reactlog
In reactlog, the dependency on slot
a shows as reactives$a, or as reactive_vals$a or reactive_exprs$a in
a collection of that class, the one on which slots exist and in what order
as names(reactives), and the one on their number as length(reactives).
Unnamed slots show as reactives$...1, reactives$...2 and so on, numbered
in the order they were added rather than by position, so a label survives
reordering. A read of every slot at once, such as as.list(), shows a
single dependency on reactives[] instead of one per slot. The entry for a
slot only appears once it has been read, and the one for the number of
slots once length() has been called.
Each shiny::reactiveVal() that reactive_vals(), $<-, [[<- or
append_value() creates for a value shows as the label of its slot followed
by (),
reactive_vals$a() for slot a and reactive_vals$...1() for the first
unnamed slot, and the error for calling it once its module is destroyed
names it the same way. The label names the slot the value was created for,
and stays with the shiny::reactiveVal() if that is later bound to another
slot.
Examples
x <- reactives(a = shiny::reactiveVal(1), b = shiny::reactive(2 * 21))
shiny::isolate(x$a)
#> [1] 1
shiny::isolate(x[["b"]])
#> [1] 42
# Assigning a value writes it, adding a slot if there is none
x$a <- 2
x$c <- NULL
shiny::isolate(as_values(x))
#> $a
#> [1] 2
#>
#> $b
#> [1] 42
#>
#> $c
#> NULL
#>
# A stored NULL reads as NULL, but `[` finds the slot's reactive
shiny::isolate(is.null(x$c))
#> [1] TRUE
shiny::isolate(is.null(x["c"]))
#> [1] FALSE
shiny::isolate(has_key(x, c("c", "d")))
#> [1] TRUE FALSE
# Assigning a reactive with `[<-` binds it, and assigning NULL removes a slot
x["d"] <- shiny::reactive(10 * x$a)
x["b"] <- NULL
shiny::isolate(names(x))
#> [1] "a" "c" "d"
shiny::isolate(x$d)
#> [1] 20
# Appending adds an unnamed slot
append_value(x, 3)
append_reactive(x, shiny::reactive(x$a + 1))
shiny::isolate(as_values(x))
#> $a
#> [1] 2
#>
#> $c
#> NULL
#>
#> $d
#> [1] 20
#>
#> [[4]]
#> [1] 3
#>
#> [[5]]
#> [1] 3
#>
y <- reactive_vals(n = 1, label = "one")
shiny::isolate(y$label)
#> [1] "one"
# Everything holding `y` sees the new order
z <- y
reorder(y, c("label", "n"))
shiny::isolate(names(z))
#> [1] "label" "n"
shiny::isolate(as_values(y))
#> $label
#> [1] "one"
#>
#> $n
#> [1] 1
#>
is_reactives(y)
#> [1] TRUE
# A value can't be written to a collection of reactive expressions
w <- reactive_exprs(twice = shiny::reactive(2 * y$n))
shiny::isolate(w$twice)
#> [1] 2
try(w$thrice <- 3)
#> Error : A value can't be written to a `reactive_exprs` collection, which only holds reactive expressions. Bind a reactive expression to the slot with `[<-` instead.
# A snapshot outlives the session it was taken in
snap <- NULL
shiny::testServer(
function(input, output, session) {
x <- reactives(n = shiny::reactive(input$n))
},
{
session$setInputs(n = 21)
snap <<- snapshot_reactives(x)
}
)
#> Loading required package: shiny
shiny::isolate(snap$n)
#> [1] 21