Search code examples
rshinyshinyjs

How do I reset input$variable in the Shiny Server code block?


In the Shiny App below, I have added a few buttons to my DT and with every click I would like to +/- 1 to the number column in the DT. This works, but when a button is clicked twice the second time it is not working. It works again, when first a button on another row is pressed.

I believe that the input$mydata_number_minus somehow needs to be reset in the observeEvent() code block. I have done various attempts, but none of them successful. I hope one of you can lead me to the magic trick.

library(shiny)
library(DT)
   
# Define UI
ui <- fluidPage(
  
  # Create DT with in-cell inputSelect dropdown
  DTOutput("mytable"),
  
  # Output to print reactive data frame
  verbatimTextOutput("verbotentext")
  
)

# Define server
server <- function(input, output, session) {
  
  # Define sample data
  mydata <- data.frame(
    Name = c("John", "Mary", "Bob"),
    number = 5,
    Age = c(25, 30, 35),
    Gender = c("Male", "Female", "Male"),
    Color = c("Red", "Green", "Blue")
  )
  
  rv_mydata <- reactiveVal(mydata)
  
  # Render DT
  output$mytable <- renderDT({
    mydata <- rv_mydata()
    
    # Add in-cell inputSelect dropdown to "Color" column
    mydata$Color <- paste0(
      "<select class='form-control' onchange='",
      "var index = $(this).closest(\"tr\").index();",
      "Shiny.setInputValue(\"mydata_color\",",
      "{",
      "  row_index: index,",
      "  new_value: $(this).val()",
      "});'>",
      "<option value='Red'", ifelse(mydata$Color == "Red", " selected", ""), ">Red</option>",
      "<option value='Green'", ifelse(mydata$Color == "Green", " selected", ""), ">Green</option>",
      "<option value='Blue'", ifelse(mydata$Color == "Blue", " selected", ""), ">Blue</option>",
      "</select>"
    )

    mydata$number_actions <- paste0(
      "<div style='display:flex;justify-content:center;'>",
      "<button onclick='",
      "var index = $(this).closest(\"tr\").index();",
      "Shiny.setInputValue(\"mydata_number_minus\",",
      "{",
      "  row_index: index",
      "});'>",
      "<</button>",
      "<div style='padding:0px 5px;width:20px;text-align:center;'>",
      mydata$Number,
      "</div>",
      "<button onclick='",
      "var index = $(this).closest(\"tr\").index();",
      "Shiny.setInputValue(\"mydata_number_plus\",",
      "{",
      "  row_index: index",
      "});'>",
      ">></button>",
      "</div>"
    )
    
    datatable(mydata, escape = FALSE, selection = "none", options = list(dom = 't', paging = FALSE, ordering = FALSE))
  })
  
  # Define reactive action triggered by in-cell inputSelect dropdown
  observeEvent(input$mydata_color, {
    # input$mydata_color %>% print()
    row_index <- input$mydata_color$row_index + 1
    new_value <- input$mydata_color$new_value

    mydata <- rv_mydata()
    mydata[row_index, "Color"] <- new_value
    rv_mydata(mydata)
  })
  
  observeEvent(input$mydata_number_minus, {
    row_index <- input$mydata_number_minus$row_index + 1
    
    mydata <- rv_mydata()
    mydata[row_index, "number"] <- mydata[row_index, "number"] - 1
    rv_mydata(mydata)

    input$mydata_number_minus %>% print()
    ## !! Need code to reset input$mydata_number_minus so that I can press it multiple times to execute this block
    input$mydata_number_minus %>% print()
  })
  

  observeEvent(input$mydata_number_plus, {
    row_index <- input$mydata_number_plus$row_index + 1
    
    mydata <- rv_mydata()
    mydata[row_index, "number"] <- mydata[row_index, "number"] + 1
    rv_mydata(mydata)
    
  })
  
  # Print reactive data frame
  output$verbotentext <- renderPrint({
    rv_mydata()
  })
  
}

# Run app
shinyApp(ui, server)

Solution

  • We can add nonce:Math.random() to the shiny.setInputValue so that the input is different with every click and will therefore be invalidated (and updated).

    library(shiny)
    library(DT)
    
    # Define UI
    ui <- fluidPage(
      
      # Create DT with in-cell inputSelect dropdown
      DTOutput("mytable"),
      
      # Output to print reactive data frame
      verbatimTextOutput("verbotentext")
      
    )
    
    # Define server
    server <- function(input, output, session) {
      
      # Define sample data
      mydata <- data.frame(
        Name = c("John", "Mary", "Bob"),
        number = 5,
        Age = c(25, 30, 35),
        Gender = c("Male", "Female", "Male"),
        Color = c("Red", "Green", "Blue")
      )
      
      rv_mydata <- reactiveVal(mydata)
      
      # Render DT
      output$mytable <- renderDT({
        mydata <- rv_mydata()
        
        # Add in-cell inputSelect dropdown to "Color" column
        mydata$Color <- paste0(
          "<select class='form-control' onchange='",
          "var index = $(this).closest(\"tr\").index();",
          "Shiny.setInputValue(\"mydata_color\",",
          "{",
          "  row_index: index,",
          "  new_value: $(this).val()",
          "});'>",
          "<option value='Red'", ifelse(mydata$Color == "Red", " selected", ""), ">Red</option>",
          "<option value='Green'", ifelse(mydata$Color == "Green", " selected", ""), ">Green</option>",
          "<option value='Blue'", ifelse(mydata$Color == "Blue", " selected", ""), ">Blue</option>",
          "</select>"
        )
        
        mydata$number_actions <- paste0(
          "<div style='display:flex;justify-content:center;'>",
          "<button onclick='",
          "var index = $(this).closest(\"tr\").index();",
          "Shiny.setInputValue(\"mydata_number_minus\",",
          "{",
          "  row_index: index,",
          "  nonce: Math.random()",
          "});'>",
          "<</button>",
          "<div style='padding:0px 5px;width:20px;text-align:center;'>",
          mydata$Number,
          "</div>",
          "<button onclick='",
          "var index = $(this).closest(\"tr\").index();",
          "Shiny.setInputValue(\"mydata_number_plus\",",
          "{",
          "  row_index: index,",
          "  nonce: Math.random()",
          "});'>",
          ">></button>",
          "</div>"
        )
    
        datatable(mydata,
                  escape = FALSE,
                  selection = "none",
                  options = list(dom = 't', paging = FALSE, ordering = FALSE)
                  )
      })
      
      # Define reactive action triggered by in-cell inputSelect dropdown
      observeEvent(input$mydata_color, {
        # input$mydata_color %>% print()
        row_index <- input$mydata_color$row_index + 1
        new_value <- input$mydata_color$new_value
        
        mydata <- rv_mydata()
        mydata[row_index, "Color"] <- new_value
        rv_mydata(mydata)
      })
      
      observeEvent(input$mydata_number_minus, {
        row_index <- input$mydata_number_minus$row_index + 1
        
        mydata <- rv_mydata()
        mydata[row_index, "number"] <- mydata[row_index, "number"] - 1
        rv_mydata(mydata)
        
        input$mydata_number_minus %>% print()
        ## !! Need code to reset input$mydata_number_minus so that I can press it multiple times to execute this block
        input$mydata_number_minus %>% print()
      })
      
      
      observeEvent(input$mydata_number_plus, {
        row_index <- input$mydata_number_plus$row_index + 1
        
        mydata <- rv_mydata()
        mydata[row_index, "number"] <- mydata[row_index, "number"] + 1
        rv_mydata(mydata)
        
      })
      
      # Print reactive data frame
      output$verbotentext <- renderPrint({
        rv_mydata()
      })
      
    }
    
    # Run app
    shinyApp(ui, server)