Monday, July 2, 2018

Multiple inputs to reactive value R Shiny

Leave a Comment

For part of a Shiny application I am building, I need to have the user select a directory. The directory path is stored in a reactive variable. The directory can either be selected by the user from a file window or the path can be manually entered by textInput. I have figured out how to do this, but I don't understand why the solution I have works! A minimal example of the working app:

library(shiny)  ui <- fluidPage(      actionButton("button1", "First Button"),    textInput("inText", "Input Text"),    actionButton("button2", "Second Button"),    textOutput("outText"),    textOutput("outFiles") )  server <- function(input, output) {   values <- reactiveValues(inDir = NULL)   observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})   observeEvent(input$button2, {values$inDir <- input$inText})   inPath <- eventReactive(values$inDir, {values$inDir})   output$outText <- renderText(inPath())   fileList <- reactive(list.files(path=inPath()))   output$outFiles <- renderPrint(fileList()) }  shinyApp(ui, server) 

The first thing I tried was to just use eventReactive and assign the two sources of input to the reactive variable:

server <- function(input, output) {    inPath <- eventReactive(input$button1, {tcltk::tk_choose.dir()})    inPath <- eventReactive(input$button2, {input$inText})    output$outText <- renderText(inPath())      fileList <- reactive(list.files(path=inPath()))    output$outFiles <- renderPrint(fileList())  }  

The effect of this as far as I can tell is that only one of the buttons does anything. What I don't really understand is why this doesn't work. What I thought would happen is that the first button pushed would create inPath and then subsequent pushes would update the value and trigger updates to dependent values (here output$outText). What exactly is happening here then?

The second thing I tried, which was almost there, was based off of this answer:

server <- function(input, output) {   values <- reactiveValues(inDir = NULL)   observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})   observeEvent(input$button2, {values$inDir <- input$inText})   inPath <- reactive({if(is.null(values$inDir)) return()                       values$inDir})   output$outText <- renderText(inPath())   fileList <- reactive(list.files(path=inPath()))   output$outFiles <- renderPrint(fileList()) } 

This works correctly except that it shows an "Error: invalid 'path' argument" message for list.files. I think this may mean that fileList is being evaluated with inPath = NULL. Why does this happen when I use reactive instead of eventReactive?

Thanks!

1 Answers

Answers 1

You could get rid of the inPath reactive and just use values$inDir instead. With req() you'll wait until values are available. Otherwise you'll get the same error (invalid 'path' argument).

The reactive triggers right away, while the eventReactive will wait until the given event occurs and the eventReactive is called.

And if(is.null(values$inDir)) return() won't work correctly, as it will return NULL if values$inDir is NULL, which is then passed to list.files. And list.files(NULL) gives the error: invalid 'path' argument.

Replace it with req(values$inDir) and you won't get that error.

And your example with 2 inPath - eventReactive's won't work, as the first one will be overwritten by the second one, so input$button1 won't trigger anything.

library(shiny)  ui <- fluidPage(     actionButton("button1", "First Button"),   textInput("inText", "Input Text"),   actionButton("button2", "Second Button"),   textOutput("outText"),   textOutput("outFiles") )  server <- function(input, output) {   values <- reactiveValues(inDir = NULL)    observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})   observeEvent(input$button2, {values$inDir <- input$inText})   output$outText <- renderText(values$inDir)    fileList <- reactive({     req(values$inDir);      list.files(path=values$inDir)   })    output$outFiles <- renderPrint(fileList()) }  shinyApp(ui, server) 

You could also use an eventReactive for button1 and an observeEvent for button2, but note that you need an extra observe({ inPath() }) to make it work. I prefer the above solution, as it is more clear what's happening and also less code.

server <- function(input, output) {   values <- reactiveValues(inDir = NULL)    inPath = eventReactive(input$button1, {values$inDir <- tcltk::tk_choose.dir()})   observe({     inPath()   })    observeEvent(input$button2, {values$inDir <- input$inText})   output$outText <- renderText(values$inDir)    fileList <- reactive({     req(values$inDir);      list.files(path=values$inDir)   })    output$outFiles <- renderPrint(fileList()) } 

And to illustrate why if(is.null(values$inDir)) return() won't work, consider the following function:

test <- function() { if (TRUE) return() } test() 

Although the if-condition evaluates to TRUE, there is still gonna be a return value (in this case NULL), which will be passed on to the following functions and in your case list.files, which will cause the error.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment