如何用map或apply定义多个Shiny输出元素?解决tabPanel批量创建报错
问题与解决方案
问题
尝试为列表中的每个数据框批量创建tabPanel元素,使用do.call和map组合实现时触发错误:Error in [[: attempt to select less than one element in integerOneIndex。手动创建tabPanel的代码可正常运行,但批量实现的代码报错。
原可复现代码:
library(tidyverse) library(shiny) ui <- fluidPage( uiOutput("tabs") ) server <- function(input, output) { dat <- lst(iris, mtcars) if (FALSE) { # 手动创建可行 output$tabs <- renderUI( tabsetPanel( tabPanel("iris", renderDataTable(dat$iris)), tabPanel("mtcars", renderDataTable(dat$mtcars)), ) ) } else { # 批量实现报错 output$tabs <- renderUI( do.call( tabsetPanel, map2(names(dat), map(dat, renderDataTable), tabPanel) ) ) } } shinyApp(ui = ui, server = server)
原因分析
报错根源是map(dat, renderDataTable)提前执行了renderDataTable,生成的是反应式输出对象列表,直接传递给tabPanel会导致Shiny在解析时出现索引定位错误——反应式对象需要在正确的UI上下文(即tabPanel内部)中创建,而非提前生成后传入。
解决方案
使用purrr::imap(或调整map2的参数传递逻辑),在遍历数据框的同时,在tabPanel内部创建renderDataTable,确保反应式上下文正确。
方案1:用imap实现(推荐)
imap可以同时获取数据框的名称和对应数据,代码更简洁:
library(tidyverse) library(shiny) ui <- fluidPage( uiOutput("tabs") ) server <- function(input, output) { dat <- lst(iris, mtcars) output$tabs <- renderUI( do.call( tabsetPanel, imap(dat, function(data, tab_name) { tabPanel(title = tab_name, renderDataTable(data)) }) ) ) } shinyApp(ui = ui, server = server)
方案2:调整map2的参数传递
如果坚持使用map2,直接传递原始数据而非提前生成的反应式对象,在map2的回调函数内部创建renderDataTable:
library(tidyverse) library(shiny) ui <- fluidPage( uiOutput("tabs") ) server <- function(input, output) { dat <- lst(iris, mtcars) output$tabs <- renderUI( do.call( tabsetPanel, map2(names(dat), dat, function(tab_name, data) { tabPanel(tab_name, renderDataTable(data)) }) ) ) } shinyApp(ui = ui, server = server)
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

