I have a shiny app which has multiple selectInput and need to be dynamically updated. The R codes below show a minimum example to reproduce my question.
There are two selectInput and one button. The default value is A1 for A and B1 for B. I will update A into A2, B into B4 when I click the button. However, the B becomes B3 in the first click (wrong result), but B4 at the second click (correct result).
It is really strange behavior. How could I fix this problem? Thanks for any suggestions.
PS: I installed the latest version of shiny in github.
Screenshots for three stages (initial, first click, second click).
Example codes to reproduce my problem.
library(shiny)
df <- data.frame(
A = c('A1', 'A1', 'A2', 'A2'),
B = c('B1', 'B2', 'B3', 'B4'),
stringsAsFactors = FALSE
)
# Shiny UI
ui <- fluidPage(
selectInput('sel1', 'A', choices = NULL,
selected = NULL,
selectize = FALSE)
, selectInput('sel2', 'B', choices = NULL,
selected = NULL,
selectize = FALSE)
, actionButton('clickme', 'Click me')
)
server <- function(input, output, session) {
observe({
df_a <- unique(df$A)
updateSelectInput(session, 'sel1', choices = df_a, selected = NULL)
})
observe({
req(input$sel1)
df_b <- df[df$A == input$sel1,]
df_b <- unique(df_b$B)
updateSelectInput(session, 'sel2', choices = df_b, selected = NULL)
})
observe({
req(input$clickme)
updateSelectInput(session, 'sel1', selected = 'A2')
updateSelectInput(session, 'sel2', selected = 'B4')
})
}
shinyApp(ui, server)
Answer
Here is why your code has this special behaviour: Lets go through what happens after you clicked the button.
First:
# Status:
# sel1 : choices : "A1", "A2", selected : "A1"
# sel2 : choices : "B1", "B2", selected : "B1"
observe({
req(input$clickme)
updateSelectInput(session, 'sel1', selected = 'A2')
updateSelectInput(session, 'sel2', selected = 'B4')
})
You manage to set sel1 to A2. But at the moment, sel2 does not have the choice B4, so it is set to NULL.
Second, since input$sel1 was updated, this fires:
# Status:
# sel1 : choices : "A1", "A2", selected : "A2"
# sel2 : choices : "B1", "B2", selected : NULL
observe({
req(input$sel1)
df_b <- df[df$A == input$sel1,]
df_b <- unique(df_b$B)
updateSelectInput(session, 'sel2', choices = df_b, selected = NULL)
})
Now, sel2 is updated to choices B3 and B4 and since you selected NULL, it automatically takes the first value in its choices, B3.
Here is what happens after you click the button a second time. As expected, this fires:
# Status:
# sel1 : choices : "A1", "A2", selected : "A2"
# sel2 : choices : "B3", "B4", selected : "B3"
observe({
req(input$clickme)
updateSelectInput(session, 'sel1', selected = 'A2')
updateSelectInput(session, 'sel2', selected = 'B4')
})
Now, both selections are valid and you end up with A2 and B4. And since sel1 was previously set to A2, there is no change in input$sel1, which is why the second observer does not trigger this time.
You can confirm this by adding observe({print(input$sel2)}) to your server. This gives the output
NULL
[1] "B1"
NULL
[1] "B3"
[1] "B4"
Edit
Now I had some time to think of a solution. From what I wrote above, it is clear that it cannot work the way you wrote your code. That is because whenever you try to set sel1 and sel2 in the same observer, sel2 will be reset afterwards (if sel1 changes). You'd need some kind of schedule: 1) set sel1 then 2) (possibly) update sel2 then 3) set sel2.
The following is a solution that does "delay" the setting of sel2 like I suggested. It might however not be the prettiest solution, but that lies in the twisted nature of the problem. It mainly checks the set-value for sel2 before it updates it. And depending on whether sel2 will be updated, it applies changes immediately or leaves it to the `sel2 - update.
library(shiny)
df <- data.frame(
A = c('A1', 'A1', 'A2', 'A2'),
B = c('B1', 'B2', 'B3', 'B4'),
stringsAsFactors = FALSE
)
# Shiny UI
ui <- fluidPage(
selectInput('sel1', 'A', choices = NULL,
selected = NULL,
selectize = FALSE)
, selectInput('sel2', 'B', choices = NULL,
selected = NULL,
selectize = FALSE)
, actionButton('clickme', 'Click me')
)
server <- function(input, output, session) {
choice <- NULL
observe({
df_a <- unique(df$A)
updateSelectInput(session, 'sel1', choices = df_a, selected = NULL)
})
observe({
req(input$sel1)
df_b <- df[df$A == input$sel1,]
df_b <- unique(df_b$B)
updateSelectInput(session, 'sel2', choices = df_b, selected = choice)
choice <<- NULL
})
observe({
req(input$clickme)
updateSelectInput(session, 'sel1', selected = 'A2')
choice <<- "B4"
# build the set of current sel2-choices
df_b <- df[df$A == isolate(input$sel1),]
df_b <- unique(df_b$B)
if(choice %in% df_b){
updateSelectInput(session, 'sel2', choices = df_b, selected = choice)
}
})
}
shinyApp(ui, server)

No comments:
Post a Comment