EDIT: I found a solution, but I don't understand why it works. Instead of calling renderPlot(plot) in that tibble with mutate and map, I passed the plot and called that on the right hand side of assignment in the for loop. I tried it on the 'real' app I had the problem with and everything just worked. It seems like something about renderPlot or map changed over time, but I don't know what.
I'm trying to make a Shiny module that renders some plots from inside a module. In the past I've done this with the sequence of actions: plot() > renderPlot() > assign to output > plotOutput > tagList > renderUI > uiOutput. I had this working in the past, but that code no longer works (I'm moving from a deprecated Shiny Server Pro running very old packages to Posit Connect with the latest of everything).
Instead of plots, I get errors that say "attempt to select less than one element in integerOneIndex".
The good news is I get one error for each plot, so it's making the correct number of plotOutputs inside the tagList, they're just referencing the wrong thing. I've tried inspecting the elements to see if the namespaces look correct, and they're at least close enough that I can't see a problem.
My best guess is that the problem is in assigning those individual plots to the output list and retrieving them from that list and is probably related to it being inside a module, but I'm namespacing inside renderUI in the module like you're supposed to. I haven't been able to figure out how to examine the output object when I jump inside the app with browser().
Here's a reproducible example. There are 3 modules, the outer module makes a navbarMenu populated by the two inner modules which make the input widgets that go in the sidebar and the plots that go in the main panel.
library(tidyverse)
library(shiny)
# MODULES ----
# UI for number of plots ----
dynPlotsInputUI <- function(id) {
ns <- NS(id)
sliderInput(
inputId = ns("n_plots"),
label = "Number of plots",
value = 1, min = 1, max = 5
)
}
dynPlotsInput <- function(id) {
moduleServer(
id,
function(input, output, session) {
return(
list(
n_plots = reactive({ input$n_plots })
)
)
}
)
}
# plots themselves ----
dynPlotsUI <- function(id) {
ns <- NS(id)
uiOutput(ns("all_plots"))
# plotOutput(ns("test_plot"))
}
dynPlots <- function(id, plot_inputs) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
# plot > renderPlot > assign > plotOutput (> tagList > renderUI > uiOutput)
# make outputs, put in tagList, and renderUI
output$all_plots <- renderUI({
plot_stuff <-
tibble(
x = rep(seq(from = 0, to = 10, length = 100), times = plot_inputs$n_plots()),
name = rep(1:plot_inputs$n_plots(), each = 100),
y = x^(1/name)
) %>%
group_by(name) %>%
nest() %>%
ungroup() %>%
mutate(obj_name = paste0("plot_", name),
plot = map(.x = data,
.f = \(x){
x %>%
ggplot(aes(x = x, y = y)) +
geom_line(linewidth = 1.5) +
scale_color_discrete(guide = "none")
}),
render = map(.x = plot,
.f = renderPlot))
# assign to output list
for (i in 1:nrow(plot_stuff)) {
output[[ plot_stuff$obj_name[[i]] ]] <- plot_stuff$render[[i]]
}
# retrieve from output list and plotOutput > tagList
lapply(X = plot_stuff %>% pull(obj_name) %>% ns(),
FUN = plotOutput) %>%
do.call(what = tagList,
args = .)
})
}
)
}
# menu with input sidebar and plot main ----
dynPlotsMenuUI <- function(id) {
ns <- NS(id)
navbarMenu(
title = id,
tabPanel(
title = "dynamic # plots",
value = id,
sidebarPanel(
dynPlotsInputUI(ns("plot_inputs"))
),
mainPanel(
dynPlotsUI(ns("plots"))
)
)
)
}
dynPlotsMenu <- function(id) {
moduleServer(
id,
function(input, output, session) {
plot_inputs <- dynPlotsInput("plot_inputs")
dynPlots("plots", plot_inputs)
}
)
}
# ACTUAL APP ----
ui <-
navbarPage(
title = "test dynamic plots",
id = "tab_selected",
dynPlotsMenuUI("test1"),
dynPlotsMenuUI("test2")
)
# plot > renderPlot > assign to output obj > plotOutput > tagList > renderUI > uiOutput
server <- function(input, output) {
# my modules ----
dynPlotsMenu("test1")
dynPlotsMenu("test2")
}
shinyApp(ui, server)