5

I have a shiny-module which uses reactive Values to store its internal state. In the example below this is only used to output the number the input changed, but my real use-cases are more complicated.

I now want to create a function which can be used to set one of these modules to the state of the other, including the internal state - or more generally: I want to create a function updateModule, which can also update the internalt state.

So my question is: How can I access and change a modules internal reactiveValues from the outside?

Another, related question for my special purpose is: How can I prevent the internal reactiveValue from updating when updating the Input - or how can I reset it(leading back to the main question?

For now, I am aware of two possible workarounds:

  1. Store internal state in hidden Input
  2. Use the call-by-reference-logic of data.table A,B (See below). There are other ways of implementing call-by-reference, but haven't used them yet.

However I would be interested to know whether there are more direct solutions, also because the internals I want to update are more complicated lists.

Example Code

#Problem: How to change reactiveValues from the outside?

library(shiny)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
        column(width=9,
               sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
               ),
        column(width=2,
               textOutput(ns("changesCount") )
               )
        )
}

synchModule<-function(session, targetModule, oldModule){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule() )

  ##Accessing and changing internal Value of targetModule??

}

module<- function(input, output, session){
  rv<-reactiveValues(changesCount=0)

  observeEvent(input$sl,rv$changesCount<-rv$changesCount+1)

  output$changesCount=renderText(rv$changesCount)

  return(reactive({
    ret <- input$sl
    attr(ret,"changesCount")<-rv$changesCount
    ret
  }))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, synchModule(session,"module2",module1)
               )

  output$module1state=renderPrint(module1() )
  output$module2state=renderPrint(module2() )

}

shinyApp(ui, server)

Workaround 1: using hidden NumericInput

#Problem: How to change reactiveValues from the outside?
##Workaround using hidden input

library(shiny)
library(shinyjs)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
        column(width=9,
               sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
               ),
        column(width=2,
               textOutput(ns("changesCount") ),
                          hidden(numericInput(
                            ns("changesCountNumeric"), "If you can see this, you forgot useShinyjs()", 0)
                          )
               )
        )
}

synchModule<-function(session, targetModule, oldModule){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule() )

  updateNumericInput(session,ns("changesCountNumeric"), 
                     value=attr(oldModule(),"changesCount")-1) #-1 to account for updating slider itself, 

}

module<- function(input, output, session){

  observeEvent(input$sl,
               updateNumericInput(session,"changesCountNumeric", 
                                  value=input$changesCountNumeric+1)
  )

  output$changesCount=renderText(input$changesCountNumeric)

  return(reactive({
    ret <- input$sl
    attr(ret,"changesCount")<-input$changesCountNumeric
    ret
  }))

}



ui=fluidPage(
  useShinyjs(),
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, synchModule(session,"module2",module1)
               )

  output$module1state=renderPrint(module1() )
  output$module2state=renderPrint(module2() )

}

shinyApp(ui, server)

P.s: I am not sure whether to put my workarounds as solutions or not.

Julian
  • 741
  • 8
  • 19

2 Answers2

9

I haven't read through your entire post because it seems to contain several questions, but I will solve the main question, the first one in bold: How can I access and change a modules internal reactiveValues from the outside?

First of all, in order to get to the solution I propose, I want to offer a different way to return the information from a module. Rather than using a value and an attribute of the value, you can return a list, which is much easier to work with. Here is the slightly modified app:

library(shiny)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
    column(width=9,
           sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
    ),
    column(width=2,
           textOutput(ns("changesCount") )
    )
  )
}

synchModule<-function(session, targetModule, oldModule){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule$value() )

  ##Accessing and changing internal Value of targetModule??

}

module<- function(input, output, session){
  rv<-reactiveValues(changesCount=0)

  observeEvent(input$sl,rv$changesCount<-rv$changesCount+1)

  output$changesCount=renderText(rv$changesCount)

  return(list(
    value = reactive({ input$sl }),
    changes = reactive({ rv$changes }),
    print = reactive({ paste0("Num: ", input$sl, "; changes: ", rv$changesCount) })
  ))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, 
               synchModule(session,"module2",module1)
  )

  output$module1state=renderPrint(module1$print() )
  output$module2state=renderPrint(module2$print() )

}

shinyApp(ui, server)

I hope you can appreciate that this is easier to read, work with, and extend.

Now, to your main question: how to access and change a module's internal reactiveValues?

You don't. Not directly at least.

Internal state is generally best to not be modified by anyone else. There is a widely used paradigm called getters and setters methods, which is what I would use here instead. You don't directly go into another module and change it's state - that would completely violate the principle behind modules (being independent and isolated). Instead, we can have a module return a getter method - in our case, that means return its values (as I did above with the value and changes list), and also a setter method - which will be a function that someone else can call in order to set the values inside the module.

If that doesn't make 100% sense yet, here's the gist of what I mean: add this "setter" to the return list of the module:

setState = function(value, count) {
  updateSliderInput(session, "sl", value = value)
  rv$changesCount <- count - 1
}

And now we no longer need to go inside a module and directly change its state, we can simply call setState()! Here's the full modified code:

library(shiny)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
    column(width=9,
           sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
    ),
    column(width=2,
           textOutput(ns("changesCount") )
    )
  )
}

synchModule<-function(session, targetModule, oldModule){
  oldModule$setState(targetModule$value(), targetModule$count())
}

module<- function(input, output, session){
  rv<-reactiveValues(changesCount=0)

  observeEvent(input$sl,rv$changesCount<-rv$changesCount+1)

  output$changesCount=renderText(rv$changesCount)

  return(list(
    value = reactive({ input$sl }),
    count = reactive({ rv$changesCount }),
    print = reactive({ paste0("Num: ", input$sl, "; changes: ", rv$changesCount) }),
    setState = function(value, count) {
      updateSliderInput(session, "sl", value = value)
      rv$changesCount <- count - 1
    }
  ))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, 
               synchModule(session,module1,module2)
  )

  output$module1state=renderPrint(module1$print() )
  output$module2state=renderPrint(module2$print() )

}

shinyApp(ui, server)
DeanAttali
  • 25,268
  • 10
  • 92
  • 118
  • yes, I was hoping for something like a "set"-method, but did not think one could simply return it. I am still a little confused about the scopes of variables... – Julian Dec 15 '18 at 14:41
0

Now that I convinced myself that data.table can also store lists as elements, I am posting workaround 2 as an answer. However, I am still interested in more direct ways of accessing the reactiveValues inside modules.

Storing lists in data.table

test=data.table(x=1:2, y=list(list(a="dsf", b="asf"), list("2dsf")))
test
test[1,y]
test[2,y

Workaround 2: using data.table

#Problem: How to change reactiveValues from the outside?
## Using call-by-reference of data.table

library(shiny)
library(data.table)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
        column(width=9,
               sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
               ),
        column(width=2,
               textOutput(ns("changesCount") )
               )
        )
}

synchModule<-function(session, targetModule, oldModule, dt){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule() )

  dt[name==targetModule, count:=attr(oldModule(),"changesCount")-1]

}

module<- function(input, output, session, dt, id){
  rv<-reactiveValues(changesCount=dt, 
                     triggerupdate=0)

  observeEvent(input$sl,{

    rv$changesCount[name==id,count:=count+1] 
    rv$triggerupdate=rv$triggerupdate+1
                     })

  output$changesCount=renderText({
    rv$triggerupdate
    rv$changesCount[name==id, count]
    })

  return(reactive({
    ret <- input$sl
    attr(ret,"changesCount")<-rv$changesCount[name==id,count]
    ret
  }))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state"),
  p(),
  p("dt doesn't refresh if not triggered:"),
  tableOutput("dtstate"),
  actionButton("RefreshDtButton", "Show and refresh state of dt"),
  tableOutput("dtstate2")

)

server= function(input, output, session) {
  dt<-data.table(name=c("module1","module2"),
                 count=0)
  module1<-callModule(module,"module1",dt,"module1") #id must be repeated
  module2<-callModule(module,"module2", dt, "module2")

  observeEvent(input$synchButton, synchModule(session,"module2",module1, dt)
               )
  observeEvent(input$RefreshDtButton, output$dtstate2<-renderTable(dt))

  output$module1state=renderPrint(module1() )
  output$module2state=renderPrint(module2() )
  output$dtstate=renderTable(dt) ##No reactivity  without triggering with data.table


}

shinyApp(ui, server)
Julian
  • 741
  • 8
  • 19