Shiny selectInput selected = NULL


#1

I have seen this question posed in StackOverflow and also as an issue in GitHub github issue link but I haven't found a real answer...
Why doesn't the Shiny selectInput option "selected = NULL" actually set the input to NULL? See the reprex below. I know the solution is to add a fake blank or "select a value below" entry to the vector of selectInput choices but I'm still curious why selected = NULL doesn't work. Thanks!

library(shinydashboard)

shinyUI <- dashboardPage(title = "Test",
                         dashboardHeader(title = "Testing selectInput"),
                         dashboardSidebar(selectInput("test","Select a letter",
                                                      LETTERS[1:6], 
                                                      selected = NULL)),
                         dashboardBody(
                           fluidRow(
                             box(uiOutput("showSelected"))
                           )
                         )
)

shinyServer <- function(input, output, session){
  
  output$showSelected <- renderUI({
    if (is.null(input$test)){
      return(tags$h3("Nothing has been selected yet"))
    } else return(tags$h3(paste0("selected letter: ",input$test)))
  })
}

shinyApp(ui=shinyUI, server = shinyServer)

#2

I think this is expected behaviour as per the documentation, although perhaps the wording of the documentation is subject to criticism.

It states:

selected
The initially selected value (or multiple values if multiple = TRUE). If not specified then defaults to the first value for single-select lists and no values for multiple select lists.

Since the default argument is NULL this qualifies as not specified and hence the result is expected. However you could argue that it should really say that if NULL this happens.

An alternative solution to the initialise as NULL problem, although perhaps not a satisfactory one, is to set multiple = TRUE. As stated in the documentation this will select no values for multiple select lists.

EDIT:

A further, maybe more satisfying solution should you find the need to do this a lot is to redefine selectInput.

myselectInput = function (inputId, label, choices, selected = NULL, multiple = FALSE, 
          selectize = TRUE, width = NULL, size = NULL) 
{
  selected <- restoreInput(id = inputId, default = selected)
  choices <- shiny:::choicesWithNames(choices)
  if (!is.null(selected)) {
    selected <- as.character(selected)
  }
  if (!is.null(size) && selectize) {
    stop("'size' argument is incompatible with 'selectize=TRUE'.")
  }
  selectTag <- tags$select(id = inputId, class = if (!selectize) 
    "form-control", size = size, 
    if(is.null(selected)) 
      HTML("<option style = 'display:none;' value disabled selected></option>")
    else HTML(""), 
    shiny:::selectOptions(choices, selected))
  if (multiple) 
    selectTag$attribs$multiple <- "multiple"
  res <- div(class = "form-group shiny-input-container", style = if (!is.null(width)) 
    paste0("width: ", validateCssUnit(width), ";"), shiny:::controlLabel(inputId, 
                                                                 label), div(selectTag))
  if (!selectize) 
    return(res)
  shiny:::selectizeIt(inputId, res, NULL, nonempty = !multiple && !("" %in% 
                                                              choices))
}


ui = fluidPage(
  myselectInput("ins","IN",selected = NULL, choices = c(letters[1:3]),selectize = FALSE),
  textOutput("out")
)
server = function(input,output,session){
  observe({
    print(is.null(input$ins))
  })
}
shinyApp(ui,server)

Most of this is similar to the source of selectInput apart from

 if(is.null(selected)) 
      HTML("<option style = 'display:none;' value disabled selected></option>")
    else HTML(""), 

and

 if (!is.null(selected)) {
    selected <- as.character(selected)
  }

This will display empty by default, and not allow you to choose empty once deselected. When selectize=FALSE this also returns a NULL. Curiously when selectize=TRUE the appearance is the same, but the return is not NULL. I don't know why this is though.