4
votes

In my shiny app I have a dynamic input using renderUI.

This works very well, and another part of the program captures the input of the sliders.

When the application changes of status (e.g. when the button "update model" is pressed) I still need to display / use sliders with similar labels but as they are "new" the value needs to be re-initialised to zero.

The problem is that the sliders have a memory. If I re-use the same inputId

  paste0(Labv[i], "_v",buttn)

shiny will have the old value associated to it.

Currently my code is using the variable buttn to bypass the problem: every time the status changes I create "new" sliders.

On the other hand the more the users will use the app, the more garbage will be collected into shiny.

I tried to use renderUI to send the list of elements to NULL, experimenting with sending a list of

updateTextInput(session, paste0(lbs[i],"_v",buttn),
            label = NULL,  value = NULL )

or tags$div("foo", NULL) but in each case the actual variable was rendered as text, which is worst!

# Added simplified example
library(shiny)
library(data.table)

#
dt_ = data.table( Month = month.abb[1:5],
A=rnorm(5, mean = 5, sd = 4),
B=rnorm(5, mean = 5, sd = 4),
C=rnorm(5, mean = 5, sd = 4),
D=rnorm(5, mean = 5, sd = 4),
E=rnorm(5, mean = 5, sd = 4))

dt_[,id :=.I]
dt <- copy(dt_)
setkey(dt_, "Month") 
setkey(dt, "Month")

shinyApp(
  ui = fluidPage(
fluidRow(
  column(4, 
    actionButton("saveButton", "Update Model"))),
fluidRow(
  column(6, dataTableOutput('DT')),
  column(3, br(),br(),checkboxGroupInput("pick",h6("Picker"), 
    month.abb[1:5])),  
  column(3, uiOutput('foo'))),
fluidRow(
  column(4, verbatimTextOutput('vals')))    
  ),

  server = function(session,input, output) {
    valPpu <- reactiveValues()

    valPpu$buttonF <- 1
    valPpu$dt_ <- dt_
##
  output$DT <- renderDataTable({
  if(length(input$pick) > 0 ) {
# browser()
    isolate( { labs <- input$pick } ) # 
    buttn <- valPpu$buttonF

    iter <- length(labs)   
    valLabs <- sapply(1:iter, function(i) {
            as.numeric(input[[paste0(labs[i],"_v",buttn)]]) })

    if( iter == sum(sapply(valLabs,length)) ) {        

          cPerc <- valLabs
          cPerc <- as.data.table(cPerc)
          cPercDt <- cbind(Month=labs,cPerc)

          ival <- which(dt[["Month"]] 
              %in% cPercDt[["Month"]])
          setkey(cPercDt, "Month") 
          for(j in LETTERS[1:5]) set(dt_, i=ival, 
          j=j, dt[cPercDt][[j]] * (1 + dt_[cPercDt][["cPerc"]]) )
          valPpu$dt_ <- dt_
  } }

  dt_[order(id),]
  }, options = list(
  scrollX = TRUE,
  scrollY = "250px" ,
  scrollCollapse = TRUE,
  paging = FALSE,
  searching = FALSE,
  ordering = FALSE )
)
##
  output$foo <- renderUI({
    if(is.null(input$saveButton)) { return() }
    if(length(input$pick) > 0 ) {
      labs <-  input$pick 
      iter <- length(labs)
      buttn <- isolate(valPpu$buttonF )
      valLabs <- sapply(1:iter, function(i) {
      if(is.null(input[[paste0(labs[i],"_v",buttn)]] )) {
                0
      } else {  as.numeric(input[[paste0(labs[i],"_v",buttn)]])  }
      }) 
  #
  toRender <- lapply(1:iter, function(i) {
    sliderInput(inputId = paste0(labs[i], "_v",buttn),
                label =  h6(paste0(labs[i],"")),
                min = -1,
                max = 1,
                step = 0.01,
                value = valLabs[i],
                # format = "##0.#%",
                ticks = FALSE, animate = FALSE)
                })

      toRender
    }
    })


    observe({

  if(is.null(input$saveButton)) { return() }
  if(input$saveButton < valPpu$buttonF) { return() }
  valPpu$buttonF <- valPpu$buttonF + 1
  dt <<- valPpu$dt_
# TODO: add proper saving code  
})
  }
)

In the actual app the checkboxGroupInput is also driven from the server with renderUI and is reset when the "update model" is pressed. Also, there are more "events" in the UI that I haven't added to the code.

Any idea?

1
Do you really have to use the data.table package in the example? If a simple data frame works, please just exclude irrelevant packages the next time.Yihui Xie
@yihui I use data.table for my app and the quickest way to provide you with an example was to do a bit of cut and paste. Thanks for your patience.Enzo

1 Answers

-1
votes

So your current approach actually works. FWIW, the sliders have been removed from HTML, so you do not need to worry about that. For the old values stored in input, such as input[['Jan_v1']] when the button has been clicked twice (and you only need input[['Jan_v2']]), I do not see why you care so much about them unless your total memory is less than a few kilobytes, because you only need a few bytes to store these values. It is probably true that you cannot remove these values from input, but I'd suggest you not spend time on this issue until it becomes a real problem.