Jan Stanstrup
Jan Stanstrup

Reputation: 1232

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
--------------

Upvotes: 0

Views: 1154

Answers (2)

Mikko Marttila
Mikko Marttila

Reputation: 11878

I think the problem here is that the “object creator” has a reactive dependency on the RVs, causing it to invalidate in the same cycle as the “input updater” when the “reader” updates the RVs. Then the settings are overwritten by the old input values before the update from the "input updater" takes place in the next cycle.

My interpretation of a play-by-play walkthrough would look something like this:

  1. Reader updates settings, invalidating object creator and input updater.
  2. New evaluation cycle starts.
  3. Input updater sends a message to update input from new settings, but the values in the input object have not changed yet.
  4. Object creator updates settings based on old input, invalidating settings and consequently input updater and object creator itself.
  5. Input updater sends another message to update input, this time based on the old settings that object creator just set.
  6. Object creator updates settings again, still based on old input. Invalidaiton is not triggered because the value hasn't changed.
  7. All outputs are ready, evaluation ends and session is at rest.
  8. Update messages sent by input updater arrive; only the latter is noted, which changes the input to the old value.
  9. Object creator runs and sets settings to old value. Again, invalidation of settings is not triggered because the value didn't change.
  10. Evaluation ends again, this time with no pending input messages.

To fix this, remove the RV dependencies from the “object creator”, e.g. with isolate(). I couldn’t get req() to work with isolate(), but in this case you could just drop that altogether.

Here’s a minimal example with the problem. Removing the req() here fixes it:

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)

And the log produced after clicking “read”:

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.

You can get a good look at the process with reactlog:

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

Upvotes: 2

cuttlefish44
cuttlefish44

Reputation: 6786

Your code works, but as far as I see input is static in the event. See below simple example.

ui <- fluidPage(
    sliderInput("controller", "Controller", 0, 20, 10),
    numericInput("inNumber", "Input number", 0),
)

server <- function(input, output, session) {
    settings <- reactiveValues(aaa = NULL)
    
    observe({
        settings$aaa <- input$controller + 3

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

shinyApp(ui, server)

I change controller 10 (default) to 12.

# this is console output
[1] 10 13  0
[1] 10 13  0
[1] 12 15 13
[1] 12 15 13

and the UI screen shot shows inNumber is updated to 15.
But console output shows input isn't updated immediately.
(maybe the updated value is in somewhere of session but I don't know where)

enter image description here

Upvotes: 1

Related Questions