simple counter with actionButton

2,457 views
Skip to first unread message

Patrick Toche

unread,
Dec 23, 2013, 2:26:22 PM12/23/13
to shiny-...@googlegroups.com
It took me a little while to figure out how to create a counter. My first attempts didn't work because I couldn't figure out how to use reactiveValues(), but then I found a way to do it. For the record I did a demo.

library("shiny")
runGist("https://gist.github.com/anonymous/8102844")

Let it help someone some day.

I couldn't understand these suggestions:
https://groups.google.com/forum/#!topic/shiny-discuss/nFvtX9nQaGc

but had better luck with these:
http://stackoverflow.com/questions/20452946/how-to-change-ui-interactively-in-shinyapp

Hadley Wickham

unread,
Dec 23, 2013, 5:30:58 PM12/23/13
to shiny-...@googlegroups.com
Hi Patrick,

The approach below is rather more complicated, but also more elegant because we don't have to save a file to disk.  It uses more advanced reactive concepts, but once you understand what's going on, you'll be able to use the same ideas in many other contexts.

library(shiny)

# I've simplified the UI a little so that we can focus on what's
# going on. I'm also using a slightly unusual way of creating a
# shiny app which means that you can just source a single file. 
# Instead of ui.r and server.r, I create ui and server objects
# which I supply to runApp in a special way (see below).
ui <- basicPage(
  tags$p(actionButton("increment", "+1")),
  tags$p(actionButton("decrement", "-1")),
  uiOutput("count")
)

server <- function(input, output) {
  # Here we create a new reactiveValues object to hold the value of 
  # the counter - we need that because input is read-only, but we
  # need something that's reactive.
  values <- reactiveValues(i = 1)
  
  # This is similar to before except we use i from values
  output$count <- renderText({
    paste0("i = ", values$i)
  })
  
  # The value of input$increment is not important, we just want to do
  # something when it changes (signalling that the button has been)
  # pushed - so instead of constructing a reactive expression we
  # use an observer, which will re-run the code whenever the button
  # is clicked
  observe({
    input$increment
    
    # We need to use isolate because otherwise whenever values$i changes
    # the observer will be run again and we'll get stuck in an infinite 
    # loop
    isolate({
      values$i <- values$i + 1
    })
  })
  
  observe({
    input$decrement
    isolate(values$i <- values$i - 1)
  })
}

runApp(list(ui = ui, server = server))

Hadley

Stéphane Laurent

unread,
Dec 24, 2013, 9:45:15 AM12/24/13
to shiny-...@googlegroups.com
Hadley's example is a great illustration of reactiveValues() and isolate().

Another way to make a counter is to use a non-local assignment with "<<-", an example is here: http://glimmer.rstudio.com/stla/reactive1/ 

Stéphane Laurent

unread,
Dec 24, 2013, 9:49:09 AM12/24/13
to shiny-...@googlegroups.com
Is there a difference between

    isolate({
      values$i <- values$i + 1
    })
 
and

values$i <- isolate(values$i) + 1
 
?

Hadley Wickham

unread,
Dec 24, 2013, 10:11:05 AM12/24/13
to Stéphane Laurent, shiny-...@googlegroups.com
No, and the second style is much nicer.

Hadley

--
http://had.co.nz/

Hadley Wickham

unread,
Dec 24, 2013, 10:13:34 AM12/24/13
to Stéphane Laurent, shiny-...@googlegroups.com
> Hadley's example is a great illustration of reactiveValues() and isolate().
>
> Another way to make a counter is to use a non-local assignment with "<<-",
> an example is here: http://glimmer.rstudio.com/stla/reactive1/

That only works when you have exactly one input tied to one output,
right? I can't see an obvious way to make it work if you have
increment and decrement buttons.

Hadley

--
http://had.co.nz/

Stéphane Laurent

unread,
Dec 24, 2013, 10:26:14 AM12/24/13
to shiny-...@googlegroups.com, Stéphane Laurent
What would be the problem with 

  IncreaseA <- reactive({
    input$click1
    A <<- A+1
    A
  })
  DecreaseA <- reactive({
    input$click2
    A <<- A-1
    A
  })

?

(or simply observe() instead of reactive(), just to increment/decrement).

Hadley Wickham

unread,
Dec 24, 2013, 10:30:47 AM12/24/13
to Stéphane Laurent, shiny-...@googlegroups.com
How would you call them in such away that the output gets updated?
Hadley
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "Shiny - Web Framework for R" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/shiny-discuss/IWBghVCcMnI/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> shiny-discus...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.



--
http://had.co.nz/

Stéphane Laurent

unread,
Dec 24, 2013, 10:36:52 AM12/24/13
to shiny-...@googlegroups.com, Stéphane Laurent
Ah I think I see what you mean: you mean the outputs connected to A, right ? 
Indeed, using reactiveValues() is the appropriate way to do so.

Joe Cheng

unread,
Dec 24, 2013, 1:38:20 PM12/24/13
to Stéphane Laurent, shiny-...@googlegroups.com
BTW, using reactive() instead of observe() isn't appropriate in this case; because reactive expressions run lazily and observers run eagerly, the latter is what you want.

The rule of thumb is that if you are running code for its side effects then you want an observer; if you are just doing a calculated value without side effects then you want a reactive expression.


--
You received this message because you are subscribed to the Google Groups "Shiny - Web Framework for R" group.
To unsubscribe from this group and stop receiving emails from it, send an email to shiny-discus...@googlegroups.com.

Patrick Toche

unread,
Dec 25, 2013, 2:29:52 PM12/25/13
to shiny-...@googlegroups.com
Thanks Hadley, that's great. I'm probably going to need your approach right away because I'm running into problems already with my approach...

Patrick Toche

unread,
Dec 26, 2013, 6:05:42 AM12/26/13
to shiny-...@googlegroups.com
Hi Hadley,

I tried to combine a conditional on values$i and the display button, but couldn't work it out. In words, I'm trying to achieve the following effect:

1) When the app is launched, display only an "increment" button displaying "Next".

2) After the actionButton has been pressed, display both an "increment" and "decrement" button, e.g. displaying "Next" and "Back".

The code below is a naive attempt that didn't work.

I understand why I can't condition on the value of: input$increment. Since the value of input$increment is defined only if the actionButton is displayed, I cannot use it to condition the button display. That's why my original attempt (based on the code in OP) wouldn't work. However, I'm not sure why in the code below, a modification of Hadley's code, the conditioning on values$i does not appear to work. Or am I doing something very silly? Thanks for pointers!


ui <- basicPage(
  uiOutput("buttons")
  ,
  uiOutput("count")
)

server <- function(input, output) {
  # A reactiveValues object holds the value of the counter
  values <- reactiveValues(i = 1) # would be initialized at i=0
  
  # Print the value of the counter stored in values$i
  output$count <- renderUI({
      h4(paste0("counter = ", values$i))
  })
  
  # Display buttons in the mainPanel
  output$buttons <- renderUI({
      if (values$i == 0) {
        list(actionButton("increment", "Next"))
      }
      if (values$i > 0) {list(
        actionButton("decrement", "Back")
        ,
        actionButton("increment", "Next")
      )}
  })
  
  # The observers re-run the code whenever the button is clicked
  # Use isolate to avoid getting stuck in an infinite loop
  observe({
      input$increment
      isolate(values$i <- values$i + 1)
  })
  
  observe({
      input$decrement
      isolate(values$i <- values$i - 1)
  })
}

# run ui and server together

Stéphane Laurent

unread,
Dec 26, 2013, 7:15:39 AM12/26/13
to shiny-...@googlegroups.com
Hi Patrick,

  output$buttons <- renderUI({
      if (values$i == 0) {
        return(list(actionButton("increment", "Next")))

Patrick Toche

unread,
Dec 26, 2013, 4:43:08 PM12/26/13
to shiny-...@googlegroups.com
Thanks Stéphane, that worked!

What is the explanation for the need for return() ?

Hadley Wickham

unread,
Dec 26, 2013, 5:00:07 PM12/26/13
to Patrick Toche, shiny-...@googlegroups.com
I think it's a bit more clear if you use an else clause:

output$buttons <- renderUI({
if (values$i == 0) {
list(actionButton("increment", "Next"))
} else {
list(
actionButton("decrement", "Back"),
actionButton("increment", "Next")
)
}
})

R functions return the last statement they execute, so your version
was only either returning NULL or the list with two buttons. I'd also
recommend following standard spacing/indenting conventions, as it
makes it easier to follow the flow of your code.

Hadley

Patrick Toche

unread,
Dec 26, 2013, 5:38:50 PM12/26/13
to shiny-...@googlegroups.com, Patrick Toche

R functions return the last statement they execute, so your version
was only either returning NULL or the list with two buttons. 

I get it, thanks a lot for the corrected code! 

Patrick Toche

unread,
Dec 27, 2013, 4:41:13 AM12/27/13
to shiny-...@googlegroups.com, Patrick Toche
Sorry to be a bore. I'm experiencing a problem with the suggested code. 

I initialize the counter at i = 0. Upon launch, the page properly displays a "Next" button and the counter is correctly set at i = 0. However, upon clicking the "Next" button, the counter jumps to i = 2 (rather than the desired jump to i = 1). Subsequently, as I click "Next" and "Back" the value of the counter is properly incremented and decremented. So the problem occurs only once at the very beginning.

Am I doing something silly? I hadn't noticed the problem earlier because I was initializing at i = 1, which does not seem to cause the undesired behaviour. 

Code is as before, with if/else structure and return() as suggested. But this time reactiveValues(i = 0).


ui <- basicPage(
  uiOutput("buttons")
  ,
  uiOutput("count")
)

server <- function(input, output) {
  # A reactiveValues object holds the value of the counter
  values <- reactiveValues(i = 0)
  
  # Print the value of the counter stored in values$i
  output$count <- renderUI({
      h4(paste0("counter = ", values$i))
  })
  
  # Display buttons in the mainPanel
  output$buttons <- renderUI({
    if (values$i == 0) { 
      return(list(actionButton("increment", "Next")))
    } else { 
      return(list( 
        actionButton("decrement", "Back")
        actionButton("increment", "Next") 
      ))
    } 

Stéphane Laurent

unread,
Dec 27, 2013, 11:19:43 AM12/27/13
to shiny-...@googlegroups.com
Because input$increment is "detected" whenever the button is created (though I'd prefer an explanation by a specialist.)

You can do:

observe({
         if(!is.null(input$increment) && input$increment>0)  values$i <- isolate(values$i) + 1
     })
 
and the same for input$decrement.

Hadley Wickham

unread,
Dec 27, 2013, 11:39:01 AM12/27/13
to Stéphane Laurent, shiny-...@googlegroups.com
Rather than saying it's "detected" I'd say it's activated - i.e. the
first time you add a new UI element, it will triggered any observers
listening to it. (I'm not an expert either, but I've been talking to
Winston about a similar issue). I think you're approach to resolving
it is correct, but I'd write it slightly differently:

observe({
if(is.null(input$increment) || input$increment == 0) return()
values$i <- isolate(values$i) + 1
})

In the future, shiny will have something like

observe({
if(!isInputInitialized(input$increment)) return()
values$i <- isolate(values$i) + 1
})

or even more succintly

onChange(input$increment, values$i <- values$i + 1)

See https://github.com/rstudio/shiny/pull/350 for more discussion.

Hadley

Patrick Toche

unread,
Dec 28, 2013, 10:13:20 AM12/28/13
to shiny-...@googlegroups.com, Stéphane Laurent
Stéphane, Hadley: thanks very much!

I made a demo, which for some as yet undiscovered reason is not currently working on my spark.rstudio.com server.

Until I figure out what the problem is, I have copied the code at gist.github. The option of displaying the code is not working, but the code can be read and copy-pasted from the gist:

To run:
library("shiny")

To get the code:

This is where my demo is intended to reside:

The ui.R and server.R files are there as well as inside a www subdirectory (I can see the ui.R and server.R listed inside ShinyApps at ssh to...@spark.rstudio.com but not when I go to, say, http://spark.rstudio.com/toche/demo-counter/ui.R where they should be). I updated all the R packages and did install the package shinyAce. Will investigate, fix the problem, and soon a fully-working demo will be stored there. 

Patrick Toche

unread,
Dec 28, 2013, 11:41:45 AM12/28/13
to shiny-...@googlegroups.com, Stéphane Laurent
There was a problem with the hyphen in "demo-counter", so that was easy to fix:

http://spark.rstudio.com/toche/demoCounter/

but having said that the code still doesn't display on the server as it does locally:

the ui.R and server.R files are in the demoCounter directory as well as inside the subdirectory demoCounter/www/, where I thought they had to be.

Anyways, the app works, it's just the code visualization that doesn't ...

Patrick Toche

unread,
Jan 2, 2014, 12:09:01 AM1/2/14
to shiny-...@googlegroups.com, Stéphane Laurent
The problems with displaying the source code within the app have been fixed. The updated version of the app now reside here: 


Note that the hyphen has made a comeback.

See also: 

Reply all
Reply to author
Forward
0 new messages