shinynumeric-inputuioutput

R Shiny - uiOutput causes numericInput to deselect when user is still typing in


I'm trying to create a data collection tool on R Shiny where the user can select as many categories as apply to them and then enter values for each. I've used uiOutput to allow the user to add a new category choice after clicking an action button.

For some reason, the numericInput that is created after clicking the action button will deselect after a split-second when the user is typing in a number, so it only catches one digit and you have to click it repeatedly to type in a full number.

I've tried changing the numericInput to a textInput and the same thing happens, so it's something to do with how I'm generating the uiOutput in the server, does it continually refresh and is there any way to stop it?

Example code given below, click on the new row button then try typing in the numericInput and you'll see. I have been stuck on this for ages and can't find any other questions similar so any help massively appreciated, thanks

library(tidyverse)
library(shiny)
library(shinyjs)



ui <- fluidPage(


fluidRow(wellPanel(h3("Category and quantity input"))),

wellPanel(fluidRow(column(width=4,selectInput("type0",label = h4("type"), choices= list("choice1" = 1,"choice2" = 2, "choice3"=3))),
                 column(width=4,numericInput("quantity0", label = h4("quantity"), value = 0, min=0)),
                 column(width=4,actionButton("New_row",label="Add new row"))),
        uiOutput("new_row_added")
))



server <- function(input, output) {


ids <<- NULL
observeEvent(input$New_row,{
if (is.null(ids)){
  ids <<- 1
}else{
  ids <<- c(ids, max(ids)+1)
}
output$new_row_added <- renderUI({
  tagList(
    
    lapply(1:length(ids),function(i){
      check_input_type <- paste0("type", ids[i])
      check_input_quantity <- paste0("quantity", ids[i])
      if(is.null(input[[check_input_type]])){
        # Create a div that contains 3 new sub divs
        div(fluidRow(column(width=4,
                            div(selectInput(paste0("type",ids[i]),label = "", choices= list("choice1" = 1,"choice2" = 2, "choice3"=3)))),
                     
                     column(width=4,div(numericInput(paste0("quantity",ids[i]), label = "", value = 00, min=0))))
        )
      } else {
        # Create a div that contains 3 existing sub divs
        div(fluidRow(column(width=4,
                            div(selectInput(paste0("type",ids[i]),label = "", choices= list("choice1" = 1,"choice2" = 2, "choice3"=3), selected = input[[check_input_type]]))),
                     
                     column(width=4,div(numericInput(paste0("quantity",ids[i]), label = "",  min=0, value = input[[check_input_quantity]]))))
        )
      }
    })
    
  )
})
})



}


shinyApp(ui = ui, server = server)

Solution

  • You need to isolate input[[check_input_type]]. By doing isolate(input[[check_input_type]]). If not, every time a new number is inserted inside that input, the ui will re render and cause the deselection.

    App:

    library(tidyverse)
    library(shiny)
    library(shinyjs)
    
    
    
    ui <- fluidPage(
      fluidRow(wellPanel(h3("Category and quantity input"))),
      wellPanel(
        fluidRow(
          column(width = 4, selectInput("type0", label = h4("type"), choices = list("choice1" = 1, "choice2" = 2, "choice3" = 3))),
          column(width = 4, numericInput("quantity0", label = h4("quantity"), value = 0, min = 0)),
          column(width = 4, actionButton("New_row", label = "Add new row"))
        ),
        uiOutput("new_row_added")
      )
    )
    
    
    
    server <- function(input, output) {
      ids <<- NULL
      observeEvent(input$New_row, {
        if (is.null(ids)) {
          ids <<- 1
        } else {
          ids <<- c(ids, max(ids) + 1)
        }
        output$new_row_added <- renderUI({
          tagList(
            lapply(1:length(ids), function(i) {
              check_input_type <- paste0("type", ids[i])
              check_input_quantity <- paste0("quantity", ids[i])
              if (is.null(isolate(input[[check_input_type]]))) {
                # Create a div that contains 3 new sub divs
                div(fluidRow(
                  column(
                    width = 4,
                    div(selectInput(paste0("type", ids[i]), label = "", choices = list("choice1" = 1, "choice2" = 2, "choice3" = 3)))
                  ),
                  column(width = 4, div(numericInput(paste0("quantity", ids[i]), label = "", value = 00, min = 0)))
                ))
              } else {
                # Create a div that contains 3 existing sub divs
                div(fluidRow(
                  column(
                    width = 4,
                    div(selectInput(paste0("type", ids[i]), label = "", choices = list("choice1" = 1, "choice2" = 2, "choice3" = 3), selected = isolate(input[[check_input_type]])))
                  ),
                  column(width = 4, div(numericInput(paste0("quantity", ids[i]), label = "", min = 0, value = input[[check_input_quantity]])))
                ))
              }
            })
          )
        })
      })
    }
    
    
    shinyApp(ui = ui, server = server)