Shiny动态增删子模块场景下shinyvalidate验证失效问题求助
解决Shiny子模块删除后InputValidator验证状态未更新的问题
问题分析
你当前的代码中,父模块的主InputValidator通过add_validator()添加了子模块的验证器,但删除子模块时仅调用了子模块自身的iv$disable()——这只会让子模块的验证器不再主动检查输入,但父模块的主验证器仍然持有这个子模块验证器的引用,并且由于子模块的输入控件已被删除,验证规则会一直判定该输入无效,导致主验证器的is_valid()始终返回FALSE。
解决方案
需要让子模块删除时,不仅禁用自身的验证器,还要通知父模块从主验证器中移除该子模块的验证器,同时清理父模块中存储的子模块验证器引用。
修改后的完整代码
app.R
library(shiny) library(shinyvalidate) library(bslib) # 确保加载bslib source("module.R") source("sub_module.R") # UI ui <- fluidPage( mod_parent_ui("parent"), uiOutput("test_result") ) # Server server <- function(input, output, session) { res <- mod_parent_server("parent") res$iv$enable() output$test_result <- renderUI(as.character(res$iv$is_valid())) } shinyApp(ui, server)
module.R
mod_parent_ui <- function(id){ ns <- NS(id) tagList( actionButton(ns("add_categ"), "Add category"), div(id = ns("categories"), "categories") ) } mod_parent_server <- function(id){ moduleServer( id, function(input, output, session){ ns <- session$ns iv <- shinyvalidate::InputValidator$new() # 存储所有子模块的验证器,方便后续移除 child_ivs <- reactiveVal(list()) # 定义移除子模块验证器的函数,传递给子模块 remove_child_iv <- function(target_iv) { # 从列表中移除目标验证器 current_list <- child_ivs() updated_list <- current_list[!sapply(current_list, function(iv) identical(iv, target_iv))] child_ivs(updated_list) # 从主验证器中移除 iv$remove_validator(target_iv) } observeEvent(input$add_categ, { child_id <- paste0("child", input$add_categ) shiny::insertUI( selector = paste0("#", ns("categories")), where = "afterBegin", mod_child_ui(ns(child_id)) ) # 传递移除函数给子模块 child_res <- mod_child_server(child_id, remove_iv = remove_child_iv) # 将子模块验证器添加到主验证器和存储列表中 iv$add_validator(child_res$iv) child_ivs(c(child_ivs(), child_res$iv)) }) list(iv = iv) }) }
sub_module.R
mod_child_ui <- function(id){ ns <- NS(id) bslib::accordion( id = ns("accordion_categ"), bslib::accordion_panel( title = "Category", value = "categ", tagList( textInput(ns("text"), "Text (required)"), actionButton(ns("delete"), "Delete") ) ) ) } mod_child_server <- function(id, remove_iv){ moduleServer( id, function(input, output, session){ ns <- session$ns iv <- shinyvalidate::InputValidator$new() iv$add_rule("text", shinyvalidate::sv_required(message = "请输入文本")) observeEvent(input$delete, { # 禁用自身验证器 iv$disable() # 通知父模块移除当前验证器 remove_iv(iv) # 正确删除命名空间下的折叠面板 bslib::accordion_panel_remove(ns("accordion_categ"), "categ") # 销毁子模块服务器,清理资源 session$destroy() }) list(iv = iv) }) }
关键修改说明
- 父模块新增验证器管理逻辑:
- 用
child_ivsreactiveVal存储所有子模块的验证器实例 - 定义
remove_child_iv函数,负责从存储列表和主验证器中移除指定的子模块验证器
- 用
- 子模块与父模块通信:
- 子模块服务器接收
remove_iv参数,删除时调用该函数通知父模块清理验证器
- 子模块服务器接收
- UI元素删除修正:
- 删除折叠面板时使用
ns("accordion_categ")确保命名空间正确,避免找不到目标元素
- 删除折叠面板时使用
- 资源清理:
- 调用
session$destroy()销毁子模块服务器,彻底清理子模块的所有 reactive 逻辑和资源
- 调用
测试效果:
- 添加子模块后,未输入文本时
is_valid()返回FALSE,输入文本后返回TRUE - 删除子模块后,主验证器的
is_valid()会根据剩余子模块的状态正确更新(无剩余子模块时返回TRUE)
内容的提问来源于stack exchange,提问作者tomayos
相关产品推荐
相关产品推荐

