Mark
Mark

Reputation: 2899

Dynamically rendered UI: how to delete old reactive variables on second run

Hello heroes of Stack overflow,

SHORT SUMMARY: App works great, until you change the entered number in the input field. UI re-renders great, but server side fails on stuff still in the memory it seems. Detailed explanation below:

I have a nicely working dynamic app, but I'm still dealing with a few bugs and one core problem.

The problem must be somewhere in the reactivity but I'm having a lot of difficulty to figure out what it is that Im doing wrong. I've tried dozens of things already, and none of them work, or end up breaking the app in other areas.

Here is the MAIN PROBLEM:

The app records the user click actions as 1's or 0's in a reactiveValues() list called dynamicvalues_highlight_button_sf1 and the elements are dynamically made within an lapply function that makes the dynamic observers the same way the dynamic buttons are made. When you enter a number, buttons appear and everything works perfect

UNTIL you change the number in the text field. -The buttons are updated and new amount is rendered, etc, BUT: the old dynamicvalues_highlight_button_sf1 and dynamiclist is still being printed. I am clueless why the old results are still there as well as new ones.

So instead of just the new results:

[1] "dl = 0, 0, 0, 0, 1" ## status of the current nr of elements (here its 5)
[1] "ob = 5"   ### nr of the last clicked button
[1] "-----------next click event prints the below this line-----------" 

the printout I get is old and new results:

[1] "dl = 0, 0, 0, 0, 1, 0" ## old results
[1] "ob = 5"
[1] "-----------next click event prints the below this line-----"
[1] "dl = 0, 0, 0, 0, 0, 0" ## new results
[1] "ob = 5"
[1] "-----------next click event prints the below this line-----"

I've tried things like rm(dynamicvalues_highlight_button_sf1) and rm(dynamiclist) but those can only work if the values are there, and cause a crash when the app starts since they don't.

Wrapping them inside an if(exists("dynamicvalues_highlight_button_sf1")) { } doens't work because exists seems not to work on reactivevalues lists. (I've also tried evaluate(need(...the variable..., "text")) and if(!is.null(...the variable...)){...} but all failed. Also tried to put these in different places in the server but no succes. I'm lost and my knowledge of R shiny still is too limited for this complexity it seems.

if I first enter i.e. 5, click something, and then recreate buttons for a number larger than 5 i.e. 6: BUTTON nr 6 works (gets blue etc), but buttons 1:5 DO NOT work.

I suspect the two problems are related to each other.

The UI and server are posted below. Have some fun trying it before you dive into the problem if you like.

NOTES: - posted the "minimal example" but its a rather complex app in order to have the whole functionality here. - the real app will spit the input NR out from a big modeling step rather than the input field in this demo - I annotated as much as possible for clarity - I left a little bit of code of my last attempt to solve the problem in the server.r at lines 18-25.

Thanks for any help you can offer!

UI.r

library(shiny)
library(shinydashboard)
library(shinyBS)


ui <- dashboardPage(
  dashboardHeader(title = "My Test App"),
  dashboardSidebar(
    sidebarMenu(id = "tabs", menuItem("testpage", tabName = "testpage", icon = icon("book"))
    )
  ),

  dashboardBody(
    tags$head(tags$style(HTML('.skin-blue .content-wrapper, .right-side {background-color: #ffffff; }, '))),


        tabItems(

  ### test page ###_________
      tabItem(tabName = "testpage",  

              h5("Enter desired nr of elements here"),
              textInput(inputId ="NrOfClusters", label = NULL , placeholder = "NULL"),

              fluidRow(
                column(2,
                       uiOutput("buttons_highlight_sf1")),

                column(1,
                    uiOutput("button_hightlight_all_sf1"),
                    uiOutput("multi_highlight"),
                    br(),
                    actionButton(inputId = "statuscheck", label = "status", style = "background-color: white")
                ))))))

SERVER.R

shinyServer = function(input, output, session) {

  ################# start functionality HOME TAB #############################  

  ### create 2 reactive environment lists
  values <- reactiveValues()
  dynamicvalues_highlight_button_sf1 <- reactiveValues()

  ### set initial state of two buttons 
  values$HL_multi_switch_sf1 <- FALSE
  values$HL_all_switch_sf1 <- FALSE 

  ### if the user types in a value, then convert it to a reactive value of this nr
  observeEvent (input$NrOfClusters, {
    isolate(values$nrofelements <- paste0(input$NrOfClusters))

    ##TRY THERE TO REMOVE THE dynamiclist and all the reactive elements in dynamic_highlight_button_sf1
    if (exists("dynamiclist")) { 

      rm(dynamiclist)
      rm(dynamicvalues_highlight_button_sf1)
      dynamicvalues_highlight_button_sf1 <- reactiveValues() } 

    isolate( dynamiclist <- as.character(unlist(reactiveValuesToList(dynamicvalues_highlight_button_sf1), use.names = FALSE)))
    isolate( print(paste0("dl length = ", length(dynamiclist))))
    })


#### RENDER DYNAMIC UI and DYNAMIC OBSERVERS
  observeEvent(values$nrofelements, {
   print(values$nrofelements == 1 | values$nrofelements >1)

    ### create a nr of buttons equal to the entered value
    if (values$nrofelements == 1 | values$nrofelements >1) { 

      output$buttons_highlight_sf1 <- renderUI({

        lapply(1:values$nrofelements, function(ab) {
          if (!is.null(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ab)]])) { 
            if(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ab)]] == 0 ) {
              div(br(), actionButton(inputId = paste0("highlight_button_sf1", ab), label = icon("lightbulb-o"),style = "color: grey;
                          background-color: white;
                                   height: 35px;
                                   width: 35px;
                                   text-align:center;
                                   text-highlight_buttonent: 0,5px;
                                   border-radius: 6px;
                                   display:block;
                                   margin: auto;
                                   border-width: 2px")) } 
              else { div(br(), actionButton(inputId = paste0("highlight_button_sf1", ab), label = icon("lightbulb-o"),style = "color: black;
                                  background-color: white;
                                  border-color: blue;
                                  height: 35px;
                                  width: 35px;
                                  text-align:center;
                                  text-highlight_buttonent: 0,5px;
                                  border-radius: 6px;
                                  display:block;
                                  margin: auto;
                                  border-width: 2px"))  }  }
              else { div(br(), actionButton(inputId = paste0("highlight_button_sf1", ab), label = icon("lightbulb-o"),style = "color: grey;
                                  background-color: white; 
                                  height: 35px; 
                                  width: 35px;
                                  text-align:center;
                                  text-highlight_buttonent: 0,5px;
                                  border-radius: 6px;
                                  display:block;
                                  margin: auto;
                                  border-width: 2px")) } 
            })
      })

      ### create a button to highlight all
      output$button_hightlight_all_sf1 <- renderUI({ 
        if(values$HL_all_switch_sf1 == TRUE) { 
        div( br(), actionButton(inputId = "hightlight_all_button_sf1", label = "All", style = "color: blue; background-color:white"), br())}
        else { div( br(), actionButton(inputId = "hightlight_all_button_sf1", label ="All", style = "color: grey; background-color:white"), br())}
      })

     ### create a button to enable highlight multiple or sinle boxes
       output$multi_highlight <-  renderUI({
        if(values$HL_multi_switch_sf1 == TRUE) { 
          div( br(), actionButton(inputId = "multi_highlight", label ="multi", style = "color: blue; background-color:white"), br())}
         else { div( br(), actionButton(inputId = "multi_highlight", label ="single", style = "color: green; background-color:white"), br())}
         })


    ### loop apply function over all dynamically created buttons
    isolate(lapply(1:values$nrofelements,  function(ob) {
      observeEvent(input[[paste0("highlight_button_sf1", ob)]], {

    ### complex observer structure to check what to do depending on the ALL and MULTI status
    ### FALSE all FALSE multi 
    if (values$HL_all_switch_sf1 == FALSE) {
            if (values$HL_multi_switch_sf1 == FALSE) { 
                for (each in 1:values$nrofelements) { 
                  if ( ob != each) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", each)]] <- 0}
                  else if (ob == each) { 
                        if (is.null(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]])) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
                        else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 1) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 0}
                        else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 0) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
                  }}}
      ### FALSE all TRUE multi
            if (values$HL_multi_switch_sf1 == TRUE){
                        if (is.null(dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]])) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
                        else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 1) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 0}
                        else if (dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] == 0) {dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 1}
            }}

    ### TRUE all TRUE multi
    if(values$HL_all_switch_sf1 == TRUE) { 
            if (values$HL_multi_switch_sf1 == TRUE) {
              dynamicvalues_highlight_button_sf1[[paste0("highlight_button", ob)]] <- 0
              isolate(values$HL_all_switch_sf1 <- FALSE)}
    ### TRUE all FALSE multi
    else if (values$HL_multi_switch_sf1 == FALSE) { for (each in 1:values$nrofelements) 
        {if (ob != each) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", each)]] <- 0}
        }
             isolate(values$HL_all_switch_sf1 <- FALSE)
    }}


      dynamiclist <- as.character(unlist(reactiveValuesToList(dynamicvalues_highlight_button_sf1), use.names = FALSE))
      print(paste0("dl = ", toString(dynamiclist)))

        print(paste("ob =", ob ))

        lastclicked_button_nr <- ob

        colorpalette <- vector(mode="character", length=values$nrofelements)
        colorpalette <- replace(colorpalette, colorpalette == "", "GREY")
        colorpalette[values$button_nr_clicked]="RED"
        print( "-----------next click event prints the below this line--------------------------------------------------------------")
      })
      }))
      }
      })


#### OBSERVE DYNAMIC UI

observeEvent(input$multi_highlight, { 
  if (values$HL_multi_switch_sf1 == TRUE) { values$HL_multi_switch_sf1 <- FALSE }
  else if (values$HL_multi_switch_sf1 == FALSE) { values$HL_multi_switch_sf1 <- TRUE }
})



observeEvent(input$hightlight_all_button_sf1,{
  if (values$HL_all_switch_sf1 == TRUE) { values$HL_all_switch_sf1 <- FALSE }
  else if (values$HL_all_switch_sf1 == FALSE) {values$HL_all_switch_sf1 <- TRUE}


  if (values$HL_all_switch_sf1 == TRUE) { for (any in 1:values$nrofelements) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", any)]] <- 1}}
  else if (values$HL_all_switch_sf1 == FALSE) { for (any in 1:values$nrofelements) { dynamicvalues_highlight_button_sf1[[paste0("highlight_button", any)]] <- 0}}

  colorpalette <- NULL
  colorpalette <- vector(mode="character", length=values$nrofelements)
  colorpalette <- replace(colorpalette, colorpalette == "", "RED")

})

### button to print the status of Multi and All on console to check what they are
observeEvent(input$statuscheck, { 
  print(paste("ALL switch: ", values$HL_all_switch_sf1)) 
  print(paste("MULTI switch: ", values$HL_multi_switch_sf1)) 
  })
}

additional bug 1: if you change the numberinput to nothing we get an error image of the NAN error

additional bug 2: if I start with entering "0" it goes well and we get no buttons, if I enter any number higher than 0 we get that many buttons, but if I then change it to 0 buttons I get 2 buttons!: bug of 2 buttons while value is 0

eventough the dynamic renderUI in line 36 of the server is wrapped inside a condtion:

if (values$nrofelements == 1 | values$nrofelements >1) { ...... 

Upvotes: 1

Views: 1349

Answers (1)

Mike Wise
Mike Wise

Reputation: 22827

Okay, your problem is a tricky one that people have fallen for before, if you look at the documentation of reactiveValues (here reactiveValues docs) it says that

"Note that values taken from the reactiveValues object are reactive, but the reactiveValues object itself is not."

So you should not be using dynamicvalues_highlight_button_sf1 the way your are, you should be using named elements of it. I got it to work by doing the following:

  • replacing dynamicvalues_highlight_button_sf1 with dhbs globally (not necssary but the lines were getting way too long for me to see what was going on).
  • replacing dhbs with dhbs$el globally.
  • getting rid of all the reactiveValuesToList calls.
  • getting rid of all the attempts to rm(...) things out of the reactive environment.
  • adding a dhbs$el <- NULL statement as the first line of the observeEvent(values$nrofelements, { node code.
  • added an extra output field to inspect dhbs with a renderTextVerbatum statement. This is a useful debugging technique when you get used to it.
  • eliminated a lot of redundant code.
  • eliminated all the isolate statements which were not doing anything.
  • added a clickcount to handle the reactivity better.

Seems to work now, although there might be a few other problems to fix up still as a result of those changes. I also think that many of those isolates are probably unnecessary and just a result of your debugging activities.

The code:

library(shiny)
library(shinydashboard)
library(shinyBS)


ui <- dashboardPage(
  dashboardHeader(title = "My Test App"),
  dashboardSidebar(
    sidebarMenu(id = "tabs", menuItem("testpage", tabName = "testpage", icon = icon("book"))
    )
  ),

  dashboardBody(
    tags$head(tags$style(HTML('.skin-blue .content-wrapper, .right-side {background-color: #ffffff; }, '))),


    tabItems(

      ### test page ###_________
      tabItem(tabName = "testpage",  

              h5("Enter desired nr of elements here"),
              textInput(inputId ="NrOfClusters", label = NULL , placeholder = "NULL"),
              verbatimTextOutput("values"),
              verbatimTextOutput("clickcount"),

              fluidRow(
                column(2,
                       uiOutput("buttons_highlight_sf1")),

                column(1,
                       uiOutput("button_hightlight_all_sf1"),
                       uiOutput("multi_highlight"),
                       br(),
                       actionButton(inputId = "statuscheck", label = "status", style = "background-color: white")
                ))))))

off_style <- 
"color: grey;
background-color: white;
height: 35px;
width: 35px;
text-align:center;
text-highlight_buttonent: 0,5px;
border-radius: 6px;
display:block;
margin: auto;
border-width: 2px"

on_style <- 
"color: grey;
background-color: white;
border-color: blue;
height: 35px;
width: 35px;
text-align:center;
text-highlight_buttonent: 0,5px;
border-radius: 6px;
display:block;
margin: auto;
border-width: 2px"


shinyServer = function(input, output, session) {

  ################# start functionality HOME TAB #############################  

  ### create 2 reactive environment lists
  values <- reactiveValues(clickcount=0)
  dhbs <- reactiveValues(el=NULL)

  ### set initial state of two buttons 
  values$HL_multi_switch_sf1 <- FALSE
  values$HL_all_switch_sf1 <- FALSE 

  ### if the user types in a value, then convert it to a reactive value of this nr
  observeEvent (input$NrOfClusters, {
    values$nrofelements <- input$NrOfClusters
    dynamiclist <- as.character(unlist(dhbs$el), use.names = FALSE)
    print(paste0("dl length = ", length(dynamiclist)))
  })

  hibutname <- function(idx){
    sprintf("highlight_button_sf1-%s-%d",values$nrofelements,idx)
  }
  atbutname <- function(idx){
    sprintf("activate_button_sf1-%s-%d",values$nrofelements,idx)
  }
  fliphib <- function(idx){
    hib <- hibutname(idx)
    dhbs$el[hib] <- abs(1-dhbs$el[hib])
  }

  sethib <- function(idx,v){
    hib <- hibutname(idx)
    dhbs$el[hib] <- v
  }


  #### RENDER DYNAMIC UI and DYNAMIC OBSERVERS
  observeEvent(values$nrofelements, {
    req(input$NrOfClusters)
    nel <- values$nrofelements
    dhbs$el <- rep(0,nel) 
    names(dhbs$el) <- sapply(1:nel,hibutname)
    print(names(dhbs$el))

    output$buttons_highlight_sf1 <- renderUI({
      values$clickcount
      print("clickcount")
      print(values$clickcount)
      lapply(1:values$nrofelements, function(ab) {
          if(dhbs$el[[hibutname(ab)]] == 0 ) {
            print("gray")
            div(br(), actionButton(inputId = hibutname(ab), label = icon("lightbulb-o"),style = off_style)) 
          } else { 
            print("black")
            div(br(), actionButton(inputId = hibutname(ab), label = icon("lightbulb-o"),style = on_style))  
          }  
      })
    })

    ### create a button to highlight all
    output$button_hightlight_all_sf1 <- renderUI({ 
      if(values$HL_all_switch_sf1 == TRUE) { 
        div( br(), actionButton(inputId = "hightlight_all_button_sf1", label = "All", style = "color: blue; background-color:white"), br())
      } else { 
        div( br(), actionButton(inputId = "hightlight_all_button_sf1", label ="All", style = "color: grey; background-color:white"), br())
      }
    })

    ### create a button to enable highlight multiple or single boxes
    output$multi_highlight <-  renderUI({
      if(values$HL_multi_switch_sf1 == TRUE) { 
        div( br(), actionButton(inputId = "multi_highlight", label ="multi", style = "color: blue; background-color:white"), br())
      }  else { 
        div( br(), actionButton(inputId = "multi_highlight", label ="single", style = "color: green; background-color:white"), br())
      }
    })


    lapply(1:values$nrofelements,  function(ob) {
      butname <- hibutname(ob)
      observeEvent(input[[butname]], {
        hibut <- hibutname(ob)
        print(hibut)
        values$clickcount <- values$clickcount+1
        print("clicked")
        print(values$clickcount)

        ### complex observer structure to check what to do depending on the ALL and MULTI status
        ### FALSE all FALSE multi 
        if (values$HL_all_switch_sf1 == FALSE) {
          if (values$HL_multi_switch_sf1 == FALSE) { 
            for (each in 1:values$nrofelements) { 
              if ( ob != each) { 
                sethib(each,0) 
              } else { 
                fliphib(each) 
              }
            }
          }
          ### FALSE all TRUE multi
          if (values$HL_multi_switch_sf1 == TRUE){
            fliphib(ob)
          }
        }

        ### TRUE all TRUE multi
        if(values$HL_all_switch_sf1 == TRUE) { 
          if (values$HL_multi_switch_sf1 == TRUE) {
            sethib(ob,0)
            values$HL_all_switch_sf1 <- FALSE
          }
          ### TRUE all FALSE multi
          else if (values$HL_multi_switch_sf1 == FALSE) { 
            for (each in 1:values$nrofelements) {
              if (ob != each) { sethib(each,0) }
            }
            values$HL_all_switch_sf1 <- FALSE
          }
        }


        dynamiclist <- as.character(unlist(dhbs$el), use.names = FALSE)
        print(paste0("dl = ", toString(dynamiclist)))

        print(paste("ob =", ob ))

        lastclicked_button_nr <- ob

        colorpalette <- vector(mode="character", length=values$nrofelements)
        colorpalette <- replace(colorpalette, colorpalette == "", "GREY")
        colorpalette[values$button_nr_clicked]="RED"
        print( "-----------next click event prints the below this line--------------------------------------------------------------")
      })
    })
  })


  #### OBSERVE DYNAMIC UI

  observeEvent(input$multi_highlight, {   values$HL_multi_switch_sf1 <- !values$HL_multi_switch_sf1 })




  observeEvent(input$hightlight_all_button_sf1,{
    values$HL_all_switch_sf1 <- !values$HL_all_switch_sf1;

    for (any in 1:values$nrofelements) { dhbs$el[[hibutname(any)]] <- as.integer(values$HL_all_switch_sf1) }

    colorpalette <- NULL
    colorpalette <- vector(mode="character", length=values$nrofelements)
    colorpalette <- replace(colorpalette, colorpalette == "", "RED")

  })

  ### button to print the status of Multi and All on console to check what they are
  observeEvent(input$statuscheck, { 
    print(paste("ALL switch: ", values$HL_all_switch_sf1)) 
    print(paste("MULTI switch: ", values$HL_multi_switch_sf1)) 
  })

  output$values <- renderPrint(as.character(unlist(dhbs$el), use.names = FALSE))
  output$clickcount <- renderPrint(values$clickcount)
}
options(shiny.reactlog = TRUE)
shinyApp(ui,shinyServer)

Screenshot:

enter image description here

Upvotes: 2

Related Questions