Reputation: 1160
Consider the following shiny app. There are three basic inputs, that the user can change: A
, M
, and S
. The "content" C (in the verbatimTextOuput
on the right) depends directly on A
and S
.
S
can be changed in two ways: by the user, or by changing M
/A
. If the user changes S
, then the dependency on M
is irrelevant. M
is also not used if it is empty.
The situation is depicted in the diagram below.
The problem is when M
is not blank and A
is changed:
C
gets updated based on the old S
and new A
S
gets updated based on M
and the new A
C
gets updated based on the new S
and new A
.Thus, C
gets updated twice, the first time with an invalid value.
What I want to happen is for S
to update, based on the new A
, then C
to update based on the new S
and new A
.
To see the problem, run the app, then:
M
boxA
C
is changed twice.How can I block the first update?
Thanks!
Shiny app code:
library(shiny)
library(digest)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("M", "M:", ""),
textInput("S", "S:", "testS"),
selectInput("A", "A:", c("A1","A2"))
),
mainPanel(
verbatimTextOutput("C")
)
)
)
server <- function(input, output, session) {
# Count calculations of C
count = 0
# Make reactive so we can modify the value
# in the input box (inpt$S is an input and output,
# essentially)
S <- reactive({
input$S
})
# Create "content" from A and S
C <- reactive({
count <<- count + 1
Sys.sleep(0.5)
message("Count ", count)
paste(
"Count: ", count, "\n", digest::sha1( c(input$A, S()) )
)
})
# When M changes, we need to change S based on A and M
# OR set S to a default value
observeEvent(input$M, {
# If user gets rid of M, reset S to default
if(input$M == ""){
S = "testS"
}else{
S = digest::sha1(c(input$M,input$A))
}
# Update the input to new S
updateTextInput(
session,
"S",
value = S
)
})
# When A changes, we need to change S based on A and M
# OR if M is blank, do nothing (S doesn't depend on M if M is blank)
observeEvent(input$A, {
# If there's no M, don't use it
if(input$M == "") return()
# Update the input to new S
updateTextInput(
session,
"S",
value = digest::sha1(c(input$M,input$A))
)
})
# "Content"
output$C <- renderText({
C()
})
}
shinyApp(ui = ui, server = server)
Upvotes: 1
Views: 514
Reputation: 878
This should work (if I've understood the logic of your function). It has a bunch of extra messages so you can see what is updated by when and in what order.
(I also changed the superassignment of count
because those just bug me, but I get the it's a little awkward with the isolate, so feel free to put it back ;)
library(shiny)
library(digest)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("M", "M:", ""),
textInput("S", "S:", "testS"),
selectInput("A", "A:", c("A1","A2"))
),
mainPanel(
verbatimTextOutput("C")
)
)
)
server <- function(input, output, session) {
# Count calculations of C
count = reactiveVal(0)
S = reactiveVal("testS")
observeEvent(input$S, { message("S updated externally")
S(input$S)
})
# When M changes, we need to change S based on A and M
# OR set S to a default value
observeEvent(input$M, { message("M updated")
# If user gets rid of M, reset S to default
if (input$M == ""){
S("testS")
} else {
S(digest::sha1(c(input$M, input$A)))
}
# Update the input to new S
updateTextInput(inputId = "S", value = S())
message("S updated by M")
})
# When A changes, we need to change S based on A and M
# OR if M is blank, do nothing (S doesn't depend on M if M is blank)
observeEvent(input$A, { message("A updated")
# If there's no M, don't use it
req(input$M)
# Update the input to new S
S(digest::sha1(c(input$M, input$A)))
updateTextInput(inputId = "S", value = S())
message("S updated by A")
})
# "Content"
output$C <- renderText({
n = isolate(count()) + 1
count(n)
#Sys.sleep(0.5)
message("Count ", n)
paste("Count: ", n, "\n", digest::sha1( c(input$A, S())))
})
}
shinyApp(ui = ui, server = server)
The S()
reactiveVal updates when the user changes input$S
externally or internally by input$M
or input$A
. The comments show which of those changes the value and output$C
only changes when input$A
or the S()
change.
Upvotes: 1
Reputation: 1579
I think the issue here is that you are using observers when you should use reactives. In general you only want to use observers for side effects (saving a file, pushing a button) not when you want a value in the app. Here I think it's better to use renderUI
to generate the UI element reactively rather than updating it with the observer.
library(shiny)
library(digest)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("M", "M:", ""),
uiOutput("S_UI"),
selectInput("A", "A:", c("A1","A2"))
),
mainPanel(
verbatimTextOutput("C")
)
)
)
server <- function(input, output, session) {
# Count calculations of C
count = 0
# Create "content" from A and S
C <- reactive({
count <<- count + 1
Sys.sleep(0.5)
message("Count ", count)
paste(
"Count: ", count, "\n", digest::sha1( c(input$A, input$S ))
)
})
output$S_UI <- renderUI({
if (input$M == "") {
val <- "testS"
} else {
val <- "S"
}
return(textInput("S", "S:", val))
})
# "Content"
output$C <- renderText({
C()
})
}
shinyApp(ui = ui, server = server)
Upvotes: 1