Updating input variable not triggering observeEvent if new value is the same

Hi all,

I have a piece of Shiny code where one observeEvent changes the choices of a selectInput that's monitored by another observeEvent. The issue is that if the default value of the new choices is the same as the currently selected one from the old one, the second observeEvent is not triggered.

Here is a reprex:

library(shiny)

ui <- fluidPage(
  selectInput("input1", "Input 1", choices = 1:5),
  selectInput("input2", "Input 2", choices = NULL),
  textOutput("output1")
)

server <- function(input, output, session) {
    
    observeEvent(input$input1, {
        updateSelectInput(session, "input2", 
                          choices = c(LETTERS[1:sample(2:6, 1)]))
    })
    
    
    observeEvent(input$input2, {
        showModal(modalDialog("Something changed in input 2"))

    }, ignoreInit = T)
    
}

shinyApp(ui, server)

When the app first loads, the modal pops up. But after that, changing the choice of Input1 will update the values of Input2, but it won't trigger the code inside it (because the choice hasn't changed). In my real example, the same option for 2 can have different results depending on what's selected in 1.

I first tried triggering the second observeEvent with both input$input1 and input$input2. This does do the job, but in case the new and old value of input2 would be different, this code would trigger the observeEvent twice...

I'm sure I'm missing something obvious, but I just can't see it :slight_smile:

Thanks,
PJ

Did you try this using observe() ?

Yep, no difference when I use that

This makes the modal message fire whether input 2 changes directly, or input1 makes input2 change (logically)

library(shiny)

ui <- fluidPage(
  selectInput("input1", "Input 1", choices = 1:5),
  selectInput("input2", "Input 2", choices = NULL),
  textOutput("output1")
)

server <- function(input, output, session) {
  
  observeEvent(   input$input1, {
    updateSelectInput(session, "input2", 
                      choices = c(LETTERS[1:sample(2:6, 1)]))
  })
  
  
  observe({
    input$input2
    input$input1
    showModal(modalDialog("Something changed in input 2"))
    
  })
  
}

shinyApp(ui, server)

Hi,

Thank for the suggestion, but like I said in my original post, this implementation will make the code fire twice when input1 changes and updates input2.

You can see that by adding this line of code to the observe:

observe({
    input$input2
    input$input1
    print("code run")
    showModal(modalDialog("Something changed in input 2"))
    
  })

Since the code to be run in the real example takes a lot of time, running it twice would slow down the app without any benefits.

PJ

I'm sorry, I'm confused by your requirements.
If the issue is that the long running calculation, would occur where the show modal occurs, then the default behaviour where one input changes the other inputs possibilities, but not its selected values, and this value is the only concern to the long running calculation, then the default shiny reactivity you started with would be the most optimal.

If you have a long running process that is triggered by changes to either input1 or input2, then you would need to accept recalculation in either such event , it would be sufficient to make a reactive involving the chosen values of input1 and input2, and this will update only when the selected values of these two inputs change, which again would be optimal.. ?

Hi,

Thanks for sticking with me here :slight_smile:
My request is like your second scenario. The code needs to update if either input1 or input2 changes.

Let me give an example.
input1 would load a file. That holds several values

input1, file1.txt

bread, 1
milk, 2
oranges, 3

input1, file2.txt

bread, 3
oranges, 3
tomatoes, 5

Input 1 lets you select a file: so the options would be: file1.txt and file2.txt
Input 2 lets you choose an item: so in case of file 1: bread, milk or oranges.

Now, the code in observeEvent for input2 uses the value associated with the item. If the code doesn't update (because both have bread as first option) then it will not trigger an update that grabs the new value for the same item name.

If you force to update on both 1 and 2 change, the code as it is now, would run twice inside obverEvent input2, because both the change in input1 and input2 trigger it. So I accept the code needed to be rerun, but not twice.

Does this help?
I'm sure I'm still missing something, because I can't believe there would be no good reason to prevent triggering code if it is updated with the same value...

PJ

Hi Pieter, I will try to produce a reprex that behaves in the way your describe, though it may take me some time because I have a few errands to run first. But I strongly believe there is an elegant solution for this.

Here is a reprex, it hopefully shows how standard reactivity can be used to create the effects you require, i.e. accurate number corresponding to the inputs choice made.
Bread appears in both files, when selected bread and switching file.
we dont need to explicitly detect a change from bread to bread, thats irrelevant, we just need that the file is changed and to look up the same bread as before in it . Hope that makes sense ?
Happy to discuss this more.

library(shiny)
library(tidyverse)


both_files <- list(
  file1 = tribble(
    ~food, ~quantity,
    "bread", 1,
    "milk", 2,
    "oranges", 3
  ),
  file2 = tribble(
    ~food, ~quantity,
    "bread", 3,
    "oranges", 3,
    "tomatoes", 5
  )
)


ui <- fluidPage(
  selectInput("input1", "Input 1", choices = c("file1", "file2")),
  uiOutput("second_selector"),
  tableOutput("output1")
)

server <- function(input, output, session) {
  
  chosen_df <- reactive({
    both_files[[req(input$input1)]]
  })

  output$second_selector <- renderUI({
    selectInput("input2", "Input 2", choices = unique(chosen_df()$food))
  })

  observeEvent(input$input2,
    {
      showModal(modalDialog("Something changed in input 2"))
    },
    ignoreInit = T
  )

  output$output1 <- renderTable({
    filter(
      chosen_df(),
      food == input$input2
    )
  })
}

shinyApp(ui, server)

Hi,

The code still suffers from the same problem, meaning that it will execute the output$output1 <- renderTable twice when you change input1 and you have a different input2 (plust the code starts with an error message, but that's besides the point).

So, if the app is running and input1 = file1 and input2 = bread, and you change input1 to file2, the code is run once. But, if input1 = file1 and input2 = oranges and you change input1 to file2, the code is run twice.

Again, the main question I have is just why explicitly updating a reactive variable to the same value is not triggering it.

Here is another reprex:

library(shiny)

ui <- fluidPage(
  
  numericInput("input1", "Set a value", value = 0),
  actionButton("click", "Click")
  
)

server <- function(input, output, session) {
  
  myVal = reactiveVal()
  
  observeEvent(input$click, {
    print("click registered")
    myVal(input$input1)
  }, ignoreInit = T)
  
  observeEvent(myVal(), {
    showModal(modalDialog("Update"))
  }, ignoreInit = T)
  
}

shinyApp(ui, server)

If you click the button once after you changed the value, there will be a pop-up. But if you click the button again, nothing will happen, although in the code the value gets updated explicitly (with the same value)

It's really a theoretical question. I know you can solve this by putting the modal in the first observeEvent that looks for the click, but the point is that I don't get it why the value wont trigger reactivity if it gets updated with the exact same value.

In my real example, there is a lot more going on and there is need to have two separate observeEvents so I can't merge them. If I do, I end up with the double execution again.

Don't worry, I did find a workaround with some additional variables, but I just would like to know why this isn't updating the way you'd expect.

PJ

this is all caused by reactivity being fundamentally lazy, so if it doesnt detect an input changed, i.e. in your recent example the numeric value, then there is nothing to do , because the present value of it is the same as the previous value, so what was calculated from it before would be calculated again and so the repetative calculation can be skipped. thats the underlying philosophy.

The particular example you had with the food and quantities, is interesting because of the interaction, and how a file choice can invalidate two inputs, (rather than just itself) thereby causing a 'doubling' of the calculations.

1 Like

Hi,

It seems that indeed there is not much else to do about this. I should reevaluate my whole app design and see if there are no other changes I can make, but I agree that the way the 'lazy' evaluation is done is probably the best scenario in general cases anyway.

Thanks for helping me look into this. It didn't really solve the issue, but I have a workaround implemented so I'm not stuck.

Grtz,
PJ

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.