Reputation: 2899
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
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!:
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
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:
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).dhbs
with dhbs$el
globally.reactiveValuesToList
calls.rm(...)
things out of the reactive environment.dhbs$el <- NULL
statement as the first line of the observeEvent(values$nrofelements, {
node code.dhbs
with a renderTextVerbatum
statement. This is a useful debugging technique when you get used to it.isolate
statements which were not doing anything.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:
Upvotes: 2