You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

关键修改说明

  1. 父模块新增验证器管理逻辑:
    • 用child_ivs reactiveVal存储所有子模块的验证器实例
    • 定义remove_child_iv函数,负责从存储列表和主验证器中移除指定的子模块验证器
  2. 子模块与父模块通信:
    • 子模块服务器接收remove_iv参数,删除时调用该函数通知父模块清理验证器
  3. UI元素删除修正:
    • 删除折叠面板时使用ns("accordion_categ")确保命名空间正确,避免找不到目标元素
  4. 资源清理:
    • 调用session$destroy()销毁子模块服务器,彻底清理子模块的所有 reactive 逻辑和资源

测试效果:

  • 添加子模块后,未输入文本时is_valid()返回FALSE,输入文本后返回TRUE
  • 删除子模块后,主验证器的is_valid()会根据剩余子模块的状态正确更新(无剩余子模块时返回TRUE)

内容的提问来源于stack exchange,提问作者tomayos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 12:29:50