-->

Can I save the old value of a reactive object when

2020-06-09 04:46发布

问题:

Note: After coming up with the answer I reworded the question to make if clearer.

Sometimes in a shiny app. I want to make use of a value selected by the user for a widget, as well as the previous value selected for that same widget. This could apply to reactive values derived from user input, where I want the old and the new value.

The problem is that if I try to save the value of a widget, then the variable containing that value has to be reactive or it will not update every time the widget changes. But, if I save the the value in a reactive context it will always give me the current value, not the previous one.

How can I save the previous value of a widget, but still have it update every time the user changes the widget?

Is there a way that does not require the use of an actionButton every time the user changes things? Avoiding an actionButton can be desirable with adding one is otherwise unnecessary and creates excess clicking for the user.

回答1:

Seeing as the session flush event method seems to be broken for this purpose, here is an alternative way to do it using an observeEvent construct and a reactive variable.

library(shiny)

ui <- fluidPage(
  h1("Memory"),
  sidebarLayout(
    sidebarPanel(
      numericInput("val", "Next Value", 10)
    ),
    mainPanel(
      verbatimTextOutput("curval"),
      verbatimTextOutput("lstval")
    )
  )
)

server <- function(input,output,session) {
  rv <- reactiveValues(lstval=0,curval=0)

  observeEvent(input$val, {rv$lstval <- rv$curval; rv$curval <- input$val})

  curre <- reactive({req(input$val);  input$val; rv$curval})
  lstre <- reactive({req(input$val);  input$val; rv$lstval})

  output$curval <- renderPrint({sprintf("cur:%d",curre())})
  output$lstval <- renderPrint({sprintf("lst:%d",lstre())})
}
options(shiny.reactlog = TRUE)
shinyApp(ui, server)

Yielding:



回答2:

Update This answer was posted before the advent of the reactiveValues/observeEvent model in shiny. I think that @MikeWise 's answer is the better way to do this.

After some playing around this is what I came up with. The ui.r is nothing special

ui.r

library(shiny)

ui <- shinyUI(fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectizeInput(inputId="XX", label="Choose a letter",choices=letters[1:5])
    ),
    mainPanel(
      textOutput("Current"),
      textOutput("old")
    )
  ) 
))  

"Current" will display the current selection and "old" displays the previous selection.

In the server.r I made use of three key functions: reactiveValues, isolate and session$onFlush.

server.r

library(shiny)

server <- function(input, output,session) {

  Values<-reactiveValues(old="Start")

  session$onFlush(once=FALSE, function(){
    isolate({ Values$old<-input$XX })
  })


  output$Current <- renderText({paste("Current:",input$XX)})

  output$old <- renderText({ paste("Old:",Values$old) })
}  

The server.r works like this.

First, Values$old is created using the reactiveValues function. I gave it the value "Start" to make it clear what was happening on load up.

Then I added a session$onFlush function. Note that I have session as an argument in my server function. This will run every time that shiny flushes the reactive system - such as when the selectizeInput is changed by the user. What is important is that it will run before input$XX gets a new value - so the value has changed at the selectizeInput but not at XX.

Inside the session$onFlush I then assign the outgoing value of XX to Values$old. This is done inside an isolate() as this will prevent any problems with input$XX gets updated with the new values. I can then use input$XX and Values$old in the renderText() functions.