daattali / shinyjs

💡 Easily improve the user experience of your Shiny apps in seconds
https://deanattali.com/shinyjs
Other
728 stars 119 forks source link

``toggleElement`` with animation makes hidden output elements render #266

Closed reijs closed 1 year ago

reijs commented 1 year ago

Description

I have used toggleElement with anim = TRUE to toggle the visibility of an output element. Surprisingly, the element is still rendered even if it is hidden in the UI. I assume this behavior is wrong. The bug does no longer happen if you set anim = FALSE.

The wrong rendering only happens for the first time you trigger the hidden output element (by changing a reactive dependency). This means, triggering it further times does no longer cause any rendering.

MWE

To see the bug behavior, please run the shiny app (code below) and follow listed steps. Note that I have added a print statement within the rendering call of the second table.

  1. Click on a row of the upper table
  2. Activate the checkbox to show the second table. You will see that the print statement is evaluated.
  3. Deactivate the checkbox to hide the second table
  4. Click on a different row of the first table. You will see that the print statement is evaluated again, even though the table is hidden in the UI.

If you set anim = FALSE and follow the same steps, the second print statement does not appear in the console.

ui <- shiny::fluidPage(
  shinyjs::useShinyjs(),
  DT::DTOutput(outputId = "mainTable"),
  shiny::checkboxInput(inputId = "checkbox", label = "Show second table"),
  shinyjs::hidden(
    shiny::div(
      DT::DTOutput(outputId = "drilldownTable"),
      id = "container"
    )
  )
)

server <- function(input, output) {
  output$mainTable <- DT::renderDT(
    expr = {
      data.frame(some_column = 1:4)
    },
    selection = "single",
  )

  shiny::observeEvent(
    eventExpr = input$checkbox,
    handlerExpr = {
      shinyjs::toggleElement(id = "container", condition = input$checkbox, anim = TRUE)
    }
  )

  output$drilldownTable <- DT::renderDT(
    expr = {
      print("Rendering the drilldown table")
      data.frame(selected_row = input$mainTable_rows_selected)
    }
  )
}

shiny::runApp(list(ui = ui, server = server))
daattali commented 1 year ago

That's an interesting bug you found! Have you been able to replicate this in a more simple setting, not with the rows selected from a table? I'm trying to reduce this bug to the simplest possible, to track down whether it's a bug in shinyjs or in shiny.

daattali commented 1 year ago

I was indeed able to replicate the problem in a much simpler code:

library(shiny)

ui <- fluidPage(
  shinyjs::useShinyjs(),
  actionButton("toggle", "toggle visibility of result"),
  numericInput("num", "num", 5),
  textOutput("result")
)

server <- function(input, output, session) {
  output$result <- renderText({
    print("rendering")
    input$num
  })
  observeEvent(input$toggle, {
    shinyjs::toggle("result", anim = TRUE)
  })
}

shinyApp(ui, server)

Will investigate.

daattali commented 1 year ago

I was able to trace it down to what I would consider a bug in shiny , and I filed it https://github.com/rstudio/shiny/issues/3831

There is a workaround within shinyjs, wihch is to add a callback to the animation to re-trigger the "hidden" method, but this is a bit messy and it's unclear how to handle chained events and when "shown" should be triggered. In my opinion this is more of a shiny bug because shiny does not respect the trigger("hidden"). I'm closing this issue since I think it should be fixed in shiny.