title: Conditional shiny UI when multiple conditions need to be handled
body: <h2>Actual question</h2>
  
  <p>How do I design(*) a shiny app where certain UI elements depend on <em>multiple</em> conditions that need to be systematically handled?</p>
  
  <p>(*) in a maintainable way that won't drive you mad ;-)</p>
  
  <hr>
  
  <h2>Details</h2>
  
  <p>I've read <a href="http://shiny.rstudio.com/articles/dynamic-ui.html" rel="nofollow noreferrer">Build a dynamic UI that reacts to user input</a> and like <code>conditionalPanel()</code>, but I have the feeling it's too "one-dimensional" for the <a href="https://rappster.shinyapps.io/timetrackr" rel="nofollow noreferrer">timetracking app</a> I would like to build (<a href="https://github.com/rappster/timetrackr/tree/stackoverflow_20151215" rel="nofollow noreferrer">source code on GitHub</a>).</p>
  
  <p><strong>What I want to be able to do:</strong></p>
  
  <ol>
  <li><p>Have one (or more) UI element(s) that can trigger conditional UI parts:</p>
  
  <p><em>State 1</em></p>
  
  <p><a href="https://i.stack.imgur.com/J0V57.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/J0V57.png" alt="State 1"></a></p></li>
  <li><p>Those conditional UI parts usually have some input fields and at least two action buttons: <code>Create</code> and <code>Cancel</code>:</p>
  
  <p><em>State 2</em></p>
  
  <p><a href="https://i.stack.imgur.com/cu8ex.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/cu8ex.png" alt="State 2"></a></p></li>
  <li><p>If <code>Create</code> is clicked, the input should be appropriately processed (e.g. writing stuff to a DB) and then the conditional UI part should "disappear" again as its condition "expired":</p>
  
  <p><em>State 3</em></p>
  
  <p><a href="https://i.stack.imgur.com/pbxOF.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/pbxOF.png" alt="State 3"></a></p>
  
  <p><em>State 4</em></p>
  
  <p><a href="https://i.stack.imgur.com/4YD7K.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/4YD7K.png" alt="State 4"></a></p></li>
  <li><p>If <code>Cancel</code> is clicked, the UI part should "disappear" again as its condition "expired":</p>
  
  <p><em>State 4</em></p>
  
  <p><a href="https://i.stack.imgur.com/4YD7K.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/4YD7K.png" alt="State 4"></a></p></li>
  <li><p>A subsequent click on <code>Trigger</code> should "start the cycle" again</p></li>
  </ol>
  
  <p><strong>Problem with multiple dependencies and dynamic dependency states:</strong></p>
  
  <p>AFAIU, if I simply put the dependencies (i.e. <code>input$action_trigger</code>, <code>input$action_create</code> and <code>input$action_cancel</code> below) into the reactive context that builds the conditional UI, then I face multiple rounds of invalidation until all dependencies have reached a stable state (see <code>output$ui_conditional &lt;- renderUI({})</code> below). </p>
  
  <p>From a UX-perspective, this feels like having to click on elements multiple times until you get what you want (check out an example of this "multiple-clicks-necessary" behavior in my <a href="https://rappster.shinyapps.io/timetrackr" rel="nofollow noreferrer">timetracking app</a>).</p>
  
  <p>That's why I came up with the idea of introducing sort of a "dependency state clearance" layer (see <code>ui_decision &lt;- reactive({})</code> below)</p>
  
  <h2>Current solution</h2>
  
  <p>My current solution feels very wrong, very fragile and very high maintenance. You can also find it at <a href="https://github.com/rappster/stackoverflow/blob/master/shiny/conditional/2015-12-15/app.R" rel="nofollow noreferrer">GitHub</a></p>
  
  <p><strong>Globals:</strong></p>
  
  <pre><code>library(shiny)
  
  GLOBALS &lt;- list()
  GLOBALS$debug$enabled &lt;- TRUE
  
  # Auxiliary functions -----------------------------------------------------
  
  createDynamicUi_conditional &lt;- function(
    input,
    output,
    ui_decision,
    debug = GLOBALS$debug$enabled
  ) {
    if (debug) {
      message("Dynamic UI: conditional ----------")
      print(Sys.time())
    }
  
    ## Form components //
    container &lt;- list()
  
    field &lt;- "title"
    name &lt;- "Title"
    value &lt;- ""
    container[[field]] &lt;- textInput(field, name, value)
  
    field &lt;- "description"
    name &lt;- "Description"
    value &lt;- ""
    container[[field]] &lt;- textInput(field, name, value)
  
    ## Bundle in box //
    value &lt;- if (ui_decision == "hide") {
      div()
    } else if (ui_decision == "show" || ui_decision == "create") {
      container$buttons &lt;- div(style="display:inline-block",
        actionButton("action_create", "Create"),
        actionButton("action_cancel", "Cancel")
      )
      do.call(div, args = list(container, title = "conditional dynamic UI"))
    } else {
      "Not implemented yet"
    }
    # print(value)
    value
  }
  </code></pre>
  
  <p><strong>UI part:</strong></p>
  
  <pre><code># UI ----------------------------------------------------------------------
  
  ui &lt;- fluidPage(
    actionButton("action_trigger", "Trigger 1"),
    h3("Database state"),
    textOutput("result"),
    p(),
    uiOutput("ui_conditional")
  )
  </code></pre>
  
  <p><strong>Server part:</strong></p>
  
  <pre><code># Server ------------------------------------------------------------------
  
  server &lt;- function(input, output, session) {
    #####################
    ## REACTIVE VALUES ##
    #####################
  
    db &lt;- reactiveValues(
      title = "",
      description = ""
    )
  
    ui_control &lt;- reactiveValues(
      action_trigger = 0,
      action_trigger__last = 0,
      action_create = 0,
      action_create__last = 0,
      action_cancel = 0,
      action_cancel__last = 0
    )
  
    #################
    ## UI DECISION ##
    #################
  
    ui_decision &lt;- reactive({
      ## Dependencies //
      ## Trigger button:
      value &lt;- input$action_trigger
      if (ui_control$action_trigger != value) ui_control$action_trigger &lt;- value
  
      ## Create button:
      ## Dynamically created within `createDynamicUi_conditional`
      value &lt;- input$action_create
      if (is.null(value)) {
        value &lt;- 0
      }
      if (ui_control$action_create != value) {
        ui_control$action_create &lt;- value
      }
  
      ## Cancel button:
      ## Dynamically created within `createDynamicUi_conditional`
      value &lt;- input$action_cancel
      if (is.null(value)) {
        value &lt;- 0
      }
      if (ui_control$action_cancel != value) {
        ui_control$action_cancel &lt;- value
      }
  
      if (GLOBALS$debug$enabled) {
        message("Dependency clearance -----")
        message("action_trigger:")
        print(ui_control$action_trigger)
        print(ui_control$action_trigger__last)
        message("action_create:")
        print(ui_control$action_create)
        print(ui_control$action_create__last)
        message("action_cancel:")
        print(ui_control$action_cancel)
        print(ui_control$action_cancel__last)
      }
      ui_decision &lt;- if (
        c (ui_control$action_trigger == 0 &amp;&amp; ui_control$action_trigger == 0) ||
          c(
            ui_control$action_trigger &gt; 0 &amp;&amp;
              ui_control$action_trigger &lt;= ui_control$action_trigger__last &amp;&amp;
  
              ui_control$action_cancel &gt; 0 &amp;&amp;
              ui_control$action_cancel &gt; ui_control$action_cancel__last
          ) ||
          c(
            ui_control$action_create == 0 &amp;&amp;
              ui_control$action_create__last &gt; 0
          )
      ) {
        "hide"
      } else if (
        ui_control$action_trigger &gt;= ui_control$action_trigger__last &amp;&amp;
          ui_control$action_create == ui_control$action_create__last
      ) {
        ## Synchronize //
        ui_control$action_cancel__last &lt;- ui_control$action_cancel
        "show"
      } else if (
        ui_control$action_create &gt; ui_control$action_create__last
      ) {
        "create"
      } else {
        "Not implemented yet"
      }
      if (GLOBALS$debug$enabled) {
        print(ui_decision)
      }
      ## Synchronize //
      ui_control$action_trigger__last &lt;- ui_control$action_trigger
      ui_control$action_create__last &lt;- ui_control$action_create
  
      ui_decision
    })
  
    output$ui_conditional &lt;- renderUI({
      createDynamicUi_conditional(input, output, ui_decision = ui_decision())
    })
  
    #################
    ## WRITE TO DB ##
    #################
  
    writeToDb &lt;- reactive({
      ui_decision &lt;- ui_decision()
      if (ui_decision == "create") {
        db$title &lt;- input$title
        db$description &lt;- input$description
      }
    })
  
    ###################
    ## RENDER RESULT ##
    ###################
  
    output$result &lt;- renderText({
      writeToDb()
      c(
        paste0("Title: ", db$title),
        paste0("Description: ", db$description)
      )
    })
  }
  </code></pre>
  
  <p><strong>Running the app:</strong></p>
  
  <pre><code>shinyApp(ui, server)
  </code></pre>
  
  <h2>Big picture</h2>
  
  <p>This is the app that I'm actually having in mind: <a href="https://rappster.shinyapps.io/timetrackr" rel="nofollow noreferrer"><code>timetrackr</code></a></p>
  
  <p><a href="https://github.com/rappster/timetrackr/tree/stackoverflow_20151215" rel="nofollow noreferrer">Source code on GitHub</a>.</p>
  
  <p>It has been build without introducing a clearance layer as drafted above. While it does provide the desired functionality, very often, you need to click UI elements more than once until a stable dependency state is reached which is really irritating.</p>