首页 > 解决方案 > Multiple inputs to reactive value R Shiny

问题描述

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!

标签: rshiny

解决方案


您可以摆脱inPath反应式并直接使用values$inDir。与req()您一起等到值可用。否则你会得到同样的错误(invalid 'path' argument)。

reactive立即触发,而将eventReactive等待给定事件发生并调用 eventReactive。

并且if(is.null(values$inDir)) return()无法正常工作,因为如果values$inDir为 NULL,它将返回 NULL,然后将其传递给list.files. 并list.files(NULL)给出错误:invalid 'path' argument

将其替换为req(values$inDir),您将不会收到该错误。

而你的例子2 inPath - eventReactive's 将不起作用,因为第一个将被第二个覆盖,所以input$button1不会触发任何东西。

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)

您也可以使用eventReactiveforbutton1observeEventfor button2,但请注意,您需要额外observe({ inPath() })的才能使其工作。我更喜欢上面的解决方案,因为它更清楚发生了什么并且代码更少。

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())
}

为了说明为什么if(is.null(values$inDir)) return()不起作用,请考虑以下函数:

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

尽管if 条件的计算结果为TRUE,但仍然会有一个返回值(在本例中为NULL),它将传递给以下函数,在您的情况下list.files,这将导致错误。


推荐阅读