6  Additional considerations for parameters & return values

There is one point that was omitted in the previous chapters & sections about server function parameters and return values when it comes to passing reactive objects.

Tip

The additional-considerations folder of the GitHub repository linked to this book implements examples to support this chapter.

Say you have created a reactiveVal object in the main server and pass its reference as a reactive parameter to the module server function:

# -- create reactive value
foo <- reactiveVal(data.frame(id = 1:3, name = paste0("name_", 1:3)))

# -- launch module server and pass parameter
module_1_server(id = "module", trigger = foo)

Now some code block inside the module not only consumes the parameter, but modifies it:

trigger(NULL)

Back to the main server level, observe the reactiveVal:

observe(
  if(is.null(foo()))
    cat("Parameter has been overwritten with NULL! \n"))

Launching this app will output this:

Listening on http://127.0.0.1:7881
Parameter has been overwritten with NULL!

The reactiveVal has been updated / overwritten everywhere inside the app1.

There is a good article dealing with reactive object’s update: (Tomić)
Basically, reactiveVal as well as reactiveValues objects can be updated anywhere in the app.
On the opposite, reactive or eventReactive objects won’t let you update them in a different module / server:

Avis : Error in bar: unused argument (NULL)
81: observe
80: <observer:observeEvent(bar())>
1: runApp

This means that one should choose carefully the type of reactive objects that are passed to a module as function parameters or returned by a module as return values.

In most (if not all) cases, as long as you want to stick to having a single feature be managed in a single place, then the reference passed as a parameter should be the output of a reactive expression to secure that it won’t be altered out of the module server in charge of it.

And, in case the object you want to pass is a reactiveVal object, then the best option is to create a proxy reactive expression so that it can’t be updated outside of the module scope:

foo <- reactiveVal(data.frame(id = 1:3, name = paste0("name_", 1:3)))
proxy <- reactive(foo())

module_1_server(id = "module", trigger = proxy)

The exact same behavior exists with reactive return values and it is critical to always take this notion into account when you create your app module architecture and the inner reactive objects if they are meant to be part of the communication process.

Note

You may have heard or read about an approach that is actually based on this behavior.

This approach is called “stratégie du petit r” (it just means the small/little r strategy) and it is based on a r object that is declared at the top level of your application as a reactiveVal or reactiveValues .

This r object is then passed to the module server function as a parameter that will be updated inside the module server… and accessible from the outside of it, and basically anywhere it is been passed to.

I have used this approach a lot in the past, but I realized that it makes everything very complex as an application is growing. Adding values to the r object in different modules makes it quite difficult to document and keep organized. Since the values can be updated anywhere… it is “easy” to implement lazy approaches in which same values are managed and updated in different modules.
At some point, it is difficult to maintain the consistency of the application architecture.

Also – and this is to me the main reason why I’m not using this approach anymore – I feel like this is a very hacky option as a function should return its value from… the return value, not the parameter!


  1. This would not happen with a standard non-reactive parameter since you modify it within a function’s context↩︎