Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I save the old value of a reactive object when it changes?

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.

like image 600
John Paul Avatar asked Oct 17 '14 20:10

John Paul


2 Answers

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:

enter image description here

like image 74
Mike Wise Avatar answered Oct 21 '22 20:10

Mike Wise


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.

like image 41
John Paul Avatar answered Oct 21 '22 22:10

John Paul