daattali/shinyjs

Switching to a tab with an element previously hidden using addClass('hidden')

jfunction opened this issue · 1 comments

I have a 2 tab app. On the second tab I implement a radio button toggling which output element to show by using shinyjs::addClass(someID, "hidden"). When I go to the tab and try show the hidden element, I am finding that the output element isn't registered somehow and it breaks.

I made a demo of this behaviour at https://gifyu.com/image/4UCT and a reprex follows. Note that replacing the shinyjs::addClass() with the (more appropriate) shinyjs::hide() yields the expected behaviour. I am able to move on with my project, but wished to relay the information here. I'm also curious to know why.

library(tidyverse)
#> Warning: replacing previous import 'vctrs::data_frame' by 'tibble::data_frame'
#> when loading 'dplyr'
library(shiny)
library(shinyjs)
#> 
#> Attaching package: 'shinyjs'
#> The following object is masked from 'package:shiny':
#> 
#>     runExample
#> The following objects are masked from 'package:methods':
#> 
#>     removeClass, show

ui <- fluidPage(
    shinyjs::useShinyjs(),
    navbarPage("Shiny Bug",
               tabPanel(title = "Tab 1", icon = icon("globe-africa"),
                        "Select Tab 2, then the Table radio button, then click filter. Nothing updates. Select Tab 1 again and then Tab 2. Now it updates."
               ),
               tabPanel(title = "Tab 2", icon = icon("filter"),
                        sidebarLayout(
                            sidebarPanel(titlePanel("Filtering"),
                                         fluidRow(
                                             column(6, 
                                                    radioButtons("tab2Radio", label = "Output Type:", choices = c("Map", "Table"))
                                             ),
                                             column(6,
                                                    shiny::actionButton("tab2Button", label = "Filter", icon = icon("filter")),
                                             )
                                         ),
                                         fluidRow(
                                             sliderInput('tab2Slider', 'Number of points', 
                                                         min = 1, max = 10, value = 4, step = 1, dragRange = FALSE)
                                         )
                            ),
                            mainPanel(fluidRow(id = "tab2Map",   "Map Placeholder"),
                                      fluidRow(id = "tab2Table", h1(textOutput(outputId = "tab2Txt")))
                            )
                        )
               )
    )
)

server <- function(input, output, session) {
    observeEvent(input$tab2Radio, {
        if (input$tab2Radio == "Map") {
            shinyjs::addClass(id = "tab2Table", class = 'hidden')
            shinyjs::removeClass(id = "tab2Map", class = 'hidden')
        }
        else if (input$tab2Radio == "Table") {
            shinyjs::removeClass(id = "tab2Table", class = 'hidden')
            shinyjs::addClass(id = "tab2Map", class = 'hidden')
        }
    })
    observeEvent(input$tab2Button, {
        randomText <- iris %>% 
            sample_n(input$tab2Slider) %>% 
            pull(Species) %>% 
            paste(collapse = ", ")
        output$tab2Txt <- renderText(randomText)
    })
}

shinyApp(ui, server)
#> 
#> Listening on http://127.0.0.1:8278

Created on 2020-09-18 by the reprex package (v0.3.0)

I haven't looked at your code but from reading your description I think I understand what's happening. Adding/removing CSS classes doesn't do anything special. It's literally just telling JavaScript to add/remove the CSS class, and I'm certain that actually happens (if you report back and tell me that the CSS class is not being added/removed when it should, then that's certainly a bug and please report it!).

Using the shinyjs hide/show does a lot more than simply showing/hiding the element, and you just found out one of the things that it does, it ensures that Shiny is aware of the change. This is actually a reason why I personally discourage people from writing their own custom JavaScript or trying to modify shiny elements unless you have a very deep understanding of shiny, because shiny does do some clever things for performance reasons which you need to be aware of if you plan on using "low level" javascript (addClass is essentially javascript). This is preceisly the reason why I opened this issue: ThinkR-open/golem#449

You should always use show/hide when possible. The addClass function should be used for aesthetics, not for functionality, and visiblity to me falls under functionality.

By the way, from a quick glance at your shiny server code, I see a common mistake of defining an output inside an observer. That's usually not the right way to achieve what you want to achieve in shiny, but that's out of scope for me to explain here. I suggest listening to Joe Cheng's videos from the RStudio Shiny Conference where he goes over some intermediate shiny concepts and explains why this pattern is so common but can lead to bugs!