What is the best way to make a copy of a reactiveValues() variable?

I would like to make a copy of a reactiveValues() variable. How can I (best) do this?

What do you mean by a copy? What are you trying to achieve?

In the meantime I have found a solution to my question. I'll clarify what I was trying to achieve using a minimal example and my current solution.

I am using a modal dialog to allow a user to change a range of settings (e.g. plot properties). In the modal dialog there are input elements to change the different settings. On loading the modal dialog each of the input elements is filled with the current setting. After making some changes in the modal dialog the user can choose to either discard or submit the changes made in the modal dialog. Only in the latter case the application is impacted.

A minimal example:

ui <- fluidPage(
  
  fluidRow(
    
    column(width = 12,
           
           actionButton(inputId = "open_settings",
                        label = "Change settings"),
           
           p("The first setting is: ", tags$b(textOutput("setting01", inline = TRUE))),
           
           p("The second setting is: ", tags$b(textOutput("setting02", inline = TRUE)))
           
    )
    
  )
  
)

server <- function(input, output, session) {
  
  # current settings (also the default settings upon opening the application)
  current_settings <- reactiveValues(
    setting01 = "dogs",
    setting02 = "sunny"
  )
  
  # open the settings modal upon clicking 'Change settings'
  new_settings <- reactiveValues()
  
  observeEvent(input$open_settings, {
    
    # copy reactiveValues() to reactiveValues()
    for (var in names(current_settings)) {
      new_settings[[var]] <- current_settings[[var]]
    }
    
    showModal(settings())
    
  })
  
  settings <- function() {
    
    modalDialog(title = "Settings",
                footer = tagList(
                  modalButton("Cancel"),
                  actionButton(inputId = "submit_settings",
                               label = "Submit")
                ),
                
                renderUI({selectInput(inputId = "new_setting01",
                            label = "Setting 01",
                            choices = c("cats", "dogs"),
                            selected = new_settings$setting01)}),
                
                renderUI({selectInput(inputId = "new_setting02",
                            label = "Setting 02",
                            choices = c("rain", "sunny"),
                            selected = new_settings$setting02)})
                
    )
    
  }
  
  # interaction between the setting inputs
  observeEvent(input$new_setting01, {
    
    # more complex interactions between new_settings$setting02 and input$new_setting01
    # can be added here (example given)
    
    if (input$new_setting01 != new_settings$setting01) { # needed to avoid a side-effect when opening the modal dialog
      new_settings$setting01 <- input$new_setting01
      
      if (input$new_setting01 == "dogs") {
        new_settings$setting02 <- "rain" # default choice for dogs
      } else if (input$new_setting01 == "cats") {
        new_settings$setting02 <- "sunny" # default choice for cats
      }
      
    }
    
    
  })
  
  observeEvent(input$new_setting02, {
    
    
    if (input$new_setting02 != new_settings$setting02) { # needed to avoid a side-effect when opening the modal dialog
      new_settings$setting02 <- input$new_setting02
      
      # more complex interactions between new_settings$setting01 and input$new_setting02
      # can be added here
      
      
    }
    
    
  })
  
  # submit the new settings
  observeEvent(input$submit_settings, {
    
    # copy reactiveValues() to reactiveValues()
    for (var in names(new_settings)) {
      current_settings[[var]] <- new_settings[[var]]
    }
    
    removeModal()
    
  })
  
  # render the settings as text for display in the main page
  output$setting01 <- renderText({
    
    current_settings$setting01
    
  })
  
  output$setting02 <- renderText({
    
    current_settings$setting02
    
  })
  
}

shinyApp(ui = ui, server = server)

To obtain the described behavior I keep the current settings in a reactiveValues() variable called 'current_settings'. The non-submitted settings I keep in a reactiveValues() variable called 'new_settings'.

Upon opening the modal dialog I need to copy the 'current_settings' variable to the 'new_settings' variable, which I currently solve in the following way

  observeEvent(input$open_settings, {
    
    # copy reactiveValues() to reactiveValues()
    for (var in names(current_settings)) {
      new_settings[[var]] <- current_settings[[var]]
    }
    
    showModal(settings())
    
  })

Upon submitting the changes I need to copy the 'new_settings' variable to the 'current_settings' variable, which I currently solve in the following way

  # submit the new settings
  observeEvent(input$submit_settings, {
    
    # copy reactiveValues() to reactiveValues()
    for (var in names(new_settings)) {
      current_settings[[var]] <- new_settings[[var]]
    }
    
    removeModal()
    
  })

I was looking for a solution where I did not have to iterate through the respective lists, but couldn't find any.

EDIT: Added an example of possible interaction between input elements in the modal dialog.

I think the recommended pattern here would be to avoid a redundant copy of settings and instead use isolate() to only update outputs when the settings are submitted. For example, something like:

 output$setting01 <- renderText({
    input$submit
    isolate(settings$setting01)
  })

I considered your recommendation but I don't see how I can avoid the copy of settings without losing the following functionality:

  • Upon opening the modal dialog all inputs initialize with the settings as shown in the main screen of the app.
  • When the value of an input element is changed by the user, the selected value for another input element always gets reset (e.g. when changing the first input element to 'dogs' (resp. 'cats') the second input element always resets to 'rainy' (resp. 'sunny')).