首页 > 解决方案 > updateNumericInput not updating from reactiveValues

问题描述

I think I have run into some strange scoping issue that I have fought with for a week now without understanding what is going on. I have also been unable to really make a small example that have the same problem but I hope the symptoms ring some bells. The real code is also available but the app is pretty complex.

Let me explain the players in the code.

  1. A number of inputs in a bsModal, mainly numericInput.

  2. An observeEvent, lets call it "the reader", fires when a file is read that contains cached results. It updates a reactiveValues object that contains the equivalent of all the inputs in a special S4 object.

  3. Then we have an observe, lets call it "the object creator" that takes all the inputs and updates the reactiveValues` object if any inputs are changed.

  4. an observeEvent, lets call it "the input updater", that fires when the reactiveValues reactive is invalidated and should update all the inputs. This is done to allow other processes to change the inputs by changing the reactiveValues reactive (for example "the object creator"). The first functionality I need is simply that it updates the inputs when the cached results are read by the "the object creator".

So it should go: "the reader" reads a file --> "the input updater" sees a new reactiveValues reactive and updates the inputs (--> the "the object creator" sees new inputs and re-writes the reactiveValues reactive but they should be what the "the reader" already set).

The issue I have is in the "the input updater". I cannot get it to update the input based on the reactiveValues.

The code looks like this:

observeEvent(settings$processing_local, {

    cat("\n\n\n")
    print("Modifying inputs")
    print(paste0("before ppm input is: ", input$local_ppm))
  
    set <- ppm(settings$processing_local) # THIS DOES NOT WORK
    print(paste0("setting: ", set)) # SHOWS CORRECT VALUE
    # set <- 1000 # THIS WORKS!
  
   updateNumericInput(session,"local_ppm",value = set)
   
   print(paste0("after ppm input is: ", input$local_ppm))
   
   cat("\n\n\n")
  
}, priority = 2)

When set is based on the reactiveValues settings$processing_local then the update doesn't happen. The crazy thing is that the output of print does show the right value AND if I hardcode a value to set then it also works. The full code for 1, 2, 3 and 4.



EDIT 1: Version of the relevant processes based on the example of @cuttlefish44 This is closer to my action app but unfortunately does not have the problem I am experiencing in the full app.

ui <- fluidPage(
    numericInput("inNumber", "Input number", 0),
    actionButton("but", "Update")
)

server <- function(input, output, session) {
    settings <- reactiveValues(aaa = NULL)
    
    
    # proxy for reading cached file
    observeEvent(input$but, {
      settings$aaa <- 30
      
    })
    

    
    observe({
      settings$aaa <- input$inNumber
      
    }, priority = 1)
    
    
    
    observeEvent(settings$aaa, {
        set <- settings$aaa
        print(c(set, input$inNumber))
        
        updateNumericInput(session, "inNumber", value = set)
        print(c(set, input$inNumber))
        
    }, priority = 2)
    
    
}

shinyApp(ui, server)



EDIT 2: In lieu of a working example of the issue I have dockerized my app so it should be possible to see the issue albeit annoying to do. Dockerized app here. Can be build with docker build --tag mscurate . and run with docker run --publish 8000:3838 mscurate. After starting the app the issue can be seen by:

  1. click "Load"
  2. Select the one available file
  3. click "local settings"
  4. Now the "ppm" value in the loaded data is 500. But the input was never updated and the reactive is then changed back to the default value of 100.

The logging shows the sequence of events when loading the file:

-------Loading started-------
before the reactive is:
settings not present
after the reactive is:
500
-------Loading finished-------

-------Modifying inputs-------
before ppm input is: 100
setting: 500
after ppm input is: 100     <---- @cuttlefish44's answer explains why this is not updated
--------------

-------updating reactive objects-------
before ppm input is: 100    <---- this should have been updated to 500!
before the reactive object is: 500
after ppm input is: 100
after the reactive object is: 100
--------------

-------Modifying inputs-------
before ppm input is: 100
setting: 100
after ppm input is: 100
--------------

-------updating reactive objects-------
before ppm input is: 100
before the reactive object is: 100
after ppm input is: 100
after the reactive object is: 100
--------------

-------updating reactive objects-------
before ppm input is: 100
before the reactive object is: 100
after ppm input is: 100
after the reactive object is: 100
--------------

标签: rshinyshiny-reactivity

解决方案


我认为这里的问题是“对象创建者”对 RVs 具有反应性依赖,导致当“阅读器”更新 RVs 时,它在与“输入更新器”相同的周期内失效。input然后在“输入更新器”的更新发生在下一个周期之前,这些设置被旧值覆盖。

我对逐个播放演练的解释如下所示:

  1. 阅读器更新设置,使对象创建者和输入更新器无效。
  2. 新的评估周期开始。
  3. 输入更新器发送消息以根据新设置更新输入,但输入对象中的值尚未更改。
  4. 对象创建者根据旧输入更新设置,使设置无效,从而使输入更新程序和对象创建者本身无效。
  5. 输入更新器发送另一条消息来更新输入,这一次基于对象创建者刚刚设置的旧设置。
  6. 对象创建者再次更新设置,仍然基于旧输入。Invalidaiton 不会被触发,因为值没有改变。
  7. 所有输出都准备就绪,评估结束,会话处于休息状态。
  8. 输入更新器发送的更新消息到达;只注意到后者,它将输入更改为旧值。
  9. 对象创建器运行并将设置设置为旧值。同样,不会触发设置失效,因为值没有改变。
  10. 评估再次结束,这次没有待处理的输入消息。

要解决此问题,请从“对象创建者”中删除 RV 依赖项,例如使用 isolate(). 我无法req()使用isolate(),但在这种情况下,您可以完全放弃它。

这是该问题的一个最小示例。删除req()此处修复它:

library(shiny)

lgr <- list(debug = function(...) cat(sprintf(...), "\n"))

ui <- fluidPage(
  sliderInput("file_number", "Number to \"read from file\"", 0, 10, 5),
  actionButton("read", "Read"),
  numericInput("number", "Input number to sync", 0)
)

server <- function(input, output, session) {
  settings <- reactiveValues(number = NULL)
  
  observeEvent(input$read, {
    settings$number <- input$file_number
    lgr$debug("Loaded settings from \"file\".")
  }, label = "reader")
  
  observe({
    req(settings$number) # The dependency on settings
    settings$number <- input$number
    lgr$debug("Updated settings from input.")
  }, priority = 1, label = "object-creator")
  
  observeEvent(settings$number, {
    updateNumericInput(session, "number", value = settings$number)
    lgr$debug("Set input from settings: %d", settings$number)
  }, priority = 2, label = "input-updater")
}

shinyApp(ui, server)

以及点击“阅读”后产生的日志:

Loaded settings from "file". 
Set input from settings: 5 
Updated settings from input. 
Set input from settings: 0 
Updated settings from input. 
Updated settings from input.

您可以使用reactlog很好地了解该过程:

reactlog::reactlog_enable()
reactlogReset()
shinyApp(ui, server)
reactlogShow()

推荐阅读