executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
build-shiny-moduleConstruir módulos Shiny reutilizables con aislamiento correcto del espacio de nombres usando NS(). Cubre pares UI/server de módulos, valores de retorno reactivos, comunicación entre módulos y composición de módulos anidados. Úsalo al extraer un componente reutilizable de una aplicación Shiny en crecimiento, al construir un widget de UI usado en múltiples lu…
Independent GitHub snapshot · Automated classification · Original documentation governs
Crear pares de módulos Shiny UI/server reutilizables con aislamiento correcto del espacio de nombres, comunicación reactiva y composabilidad.
Antes de escribir código, define qué acepta y devuelve el módulo:
Módulo: data_filter
Entradas: dataset reactivo, nombres de columnas para filtrar
Salidas: dataset filtrado reactivo
UI: controles de filtro (selectInput, sliderInput, dateRangeInput)
Esperado: Contrato claro que especifica entradas reactivas, salidas reactivas y elementos de UI.
En caso de fallo: Si la interfaz no está clara, el módulo probablemente es demasiado amplio. Divídelo en módulos más pequeños con responsabilidades únicas.
#' Data Filter Module UI
#'
#' @param id Module namespace ID
#' @return A tagList of filter controls
#' @export
dataFilterUI <- function(id) {
ns <- NS(id)
tagList(
selectInput(
ns("column"),
"Filter column",
choices = NULL
),
uiOutput(ns("filter_control")),
actionButton(ns("apply"), "Apply Filter", class = "btn-primary")
)
}
Reglas clave:
<nombre>UIidns <- NS(id) al inicioinputId y outputId con ns()tagList() para permitir ubicación flexibleEsperado: Función UI que crea elementos de entrada/salida con espacio de nombres.
En caso de fallo: Si los IDs colisionan al usar el módulo dos veces, verifica que cada ID esté envuelto con ns(). Omisión común: IDs dentro de renderUI() o uiOutput() — estos también necesitan ns().
#' Data Filter Module Server
#'
#' @param id Module namespace ID
#' @param data Reactive expression returning a data frame
#' @param columns Character vector of filterable column names
#' @return Reactive expression returning the filtered data frame
#' @export
dataFilterServer <- function(id, data, columns) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
# Update column choices when data changes
observeEvent(data(), {
available <- intersect(columns, names(data()))
updateSelectInput(session, "column", choices = available)
})
# Dynamic filter control based on selected column
output$filter_control <- renderUI({
req(input$column)
col_data <- data()[[input$column]]
if (is.numeric(col_data)) {
sliderInput(
ns("value_range"),
"Range",
min = min(col_data, na.rm = TRUE),
max = max(col_data, na.rm = TRUE),
value = range(col_data, na.rm = TRUE)
)
} else {
selectInput(
ns("value_select"),
"Values",
choices = unique(col_data),
multiple = TRUE,
selected = unique(col_data)
)
}
})
# Return filtered data as a reactive
filtered <- eventReactive(input$apply, {
req(input$column)
col <- input$column
df <- data()
if (is.numeric(df[[col]])) {
req(input$value_range)
df[df[[col]] >= input$value_range[1] &
df[[col]] <= input$value_range[2], ]
} else {
req(input$value_select)
df[df[[col]] %in% input$value_select, ]
}
}, ignoreNULL = FALSE)
return(filtered)
})
}
Reglas clave:
<nombre>ServeridmoduleServer(id, function(input, output, session) { ... })session$ns para UI dinámica creada dentro del serverEsperado: Función server que procesa entradas y devuelve salida reactiva.
En caso de fallo: Si los valores reactivos no se actualizan, verifica que las entradas de UI dinámica usen session$ns (no el ns externo). Si el módulo devuelve NULL, asegúrate de que return() sea la última expresión dentro de moduleServer().
# In app_ui.R or ui
ui <- page_sidebar(
title = "Analysis App",
sidebar = sidebar(
dataFilterUI("filter1")
),
card(
DT::dataTableOutput("table")
)
)
# In app_server.R or server
server <- function(input, output, session) {
# Raw data source
raw_data <- reactive({ mtcars })
# Call module — capture its return value
filtered_data <- dataFilterServer(
"filter1",
data = raw_data,
columns = c("cyl", "mpg", "hp", "wt")
)
# Use the module's returned reactive
output$table <- DT::renderDataTable({
filtered_data()
})
}
Esperado: El módulo aparece en la UI y su reactivo devuelto fluye hacia las salidas posteriores.
En caso de fallo: Si la UI del módulo no se renderiza, verifica que la cadena id coincida entre las llamadas UI y server. Si el reactivo devuelto es NULL, verifica que la función server realmente devuelva un valor.
Para módulos que contienen otros módulos:
analysisUI <- function(id) {
ns <- NS(id)
tagList(
dataFilterUI(ns("filter")),
plotOutput(ns("plot"))
)
}
analysisServer <- function(id, data) {
moduleServer(id, function(input, output, session) {
# Call inner module with namespaced ID
filtered <- dataFilterServer("filter", data = data, columns = names(data()))
output$plot <- renderPlot({
req(filtered())
plot(filtered())
})
return(filtered)
})
}
Regla clave: En la UI, anida con ns("inner_id"). En el server, llama con solo "inner_id" — moduleServer gestiona el encadenamiento del espacio de nombres.
Esperado: El módulo interno se renderiza correctamente dentro del espacio de nombres del módulo externo.
En caso de fallo: Si la UI del módulo interno no aparece, probablemente olvidaste ns() alrededor del ID del módulo interno en la función UI externa. Si la comunicación del server falla, verifica que el ID del módulo interno coincida (sin ns() en la llamada del server).
# Quick test app for the module
if (interactive()) {
shiny::shinyApp(
ui = fluidPage(
dataFilterUI("test"),
DT::dataTableOutput("result")
),
server = function(input, output, session) {
data <- reactive(iris)
filtered <- dataFilterServer("test", data, names(iris))
output$result <- DT::renderDataTable(filtered())
}
)
}
Esperado: El módulo funciona correctamente en la app de prueba mínima.
En caso de fallo: Si el módulo falla en aislamiento pero funciona en la app completa (o viceversa), busca dependencias implícitas en variables globales o en el estado de la sesión padre.
id como primer argumento y usa NS(id)ns()moduleServer(id, function(input, output, session) { ... })session$ns para los IDsns() en renderUI(): La UI dinámica creada dentro del server debe usar session$ns — el ns externo no está disponible dentro de moduleServer().reactive(data) no data.id en la llamada UI debe coincidir exactamente con el id en la llamada server.return() un reactivo. Olvidarlo es un bug silencioso.ns("inner_id"). En server: solo "inner_id". Mezclar esto causa doble envolvimiento del espacio de nombres o prefijos faltantes.scaffold-shiny-app — configurar la estructura de la app antes de añadir módulostest-shiny-app — probar módulos con tests unitarios testServer()design-shiny-ui — layout y temas bslib para UIs de módulosoptimize-shiny-performance — patrones de caché y async dentro de módulos