Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bslib::nav_insert() with repeated inputs fails with error console changes in shiny 1.10.0 #1159

Open
grcatlin opened this issue Jan 8, 2025 · 2 comments

Comments

@grcatlin
Copy link

grcatlin commented Jan 8, 2025

Describe the problem

I have a nav_menu() that I populate once certain actions have been taken in an app using nav_insert(). The repeated shiny::actionLink("refresh") fetches data for that page based on input$main. After updating from shiny 1.9.1, to shiny 1.10.0, this app no longer functions.

renv::activate()
# restart
renv::install(
    c("[email protected]", "bslib", "bsicons", "devtools")
)
renv::snapshot()

app.R

library(shiny)
library(bslib)
library(bsicons)

ui <- bslib::page_navbar(
    title = "Reprex",
    id = "main",
    lang = "en"
)

server <- function(input, output, session) {
    bslib::nav_insert(
        id = "main",
        nav = bslib::nav_menu(
            title = "Page",
            bslib::nav_panel(
                title = "A",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "A",
                    ),
                    bslib::nav_spacer(),
                    bslib::nav_item(
                        shiny::actionLink(
                            "refresh",
                            shiny::icon(
                                "rotate",
                                class = "actionLinkButton"
                            ),
                        )
                    )
                )
            ),
            bslib::nav_panel(
                title = "B",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "B",
                    ),
                    bslib::nav_spacer(),
                    bslib::nav_item(
                        shiny::actionLink(
                            "refresh",
                            shiny::icon(
                                "rotate",
                                class = "actionLinkButton"
                            ),
                        )
                    )
                )
            ),
            bslib::nav_panel(
                title = "C",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "C",
                    ),
                    bslib::nav_spacer(),
                    bslib::nav_item(
                        shiny::actionLink(
                            "refresh",
                            shiny::icon(
                                "rotate",
                                class = "actionLinkButton"
                            ),
                        )
                    )
                )
            ),
            bslib::nav_panel(
                title = "D",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "D",
                        bslib::card(
                            "This does not have the repeated input."
                        )
                    )
                )
            )
        )
    )

    # spacer
    bslib::nav_insert(
        id = "main",
        bslib::nav_spacer(),
    )

    # settings
    bslib::nav_insert(
        id = "main",
        bslib::nav_menu(
            title = "Settings",
            icon = bsicons::bs_icon("gear"),
            align = "right",
            bslib::nav_item(
                shiny::actionLink(
                    "user_logout",
                    "Logout",
                    icon = shiny::icon("right-from-bracket")
                )
            )
        )
    )
}

shiny::shinyApp(ui, server)

As of 1.10.0:

renv::deactivate(clean = TRUE)
# restart
renv::activate()
# restart
renv::install(
    c("shiny", "bslib", "bsicons", "devtools")
)

The first 2 nav_panels (A, B) are populated but clicking on (C, D) does nothing whatsoever. Inspecting the javascript console, there are 2 new errors:

errorConsole.ts:161 [shiny] Error on client while running Shiny app - i.get is not a function

shinyapp.ts:456 TypeError: i.get is not a function
    at Object.r [as checkValidity] (bind.ts:161:69)
    at bind.ts:445:28
    at m (bind.ts:2:1357)
    at Generator.<anonymous> (bind.ts:2:4174)
    at Generator.next (bind.ts:2:2208)
    at ZI (bind.ts:3:99)
    at s (bind.ts:4:194)

This is resolved if the action links are replaced with:

bslib::nav_item(
    shiny::icon(
        "rotate",
        class = "actionLinkButton",
        onclick = "Shiny.setInputValue('refresh', Date.now() , {priority: 'event'})"
    )
)

Updated server.R:


server <- function(input, output, session) {
    bslib::nav_insert(
        id = "main",
        nav = bslib::nav_menu(
            title = "Page",
            bslib::nav_panel(
                title = "A",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "A",
                    ),
                    bslib::nav_spacer(),
                    bslib::nav_item(
                        shiny::icon(
                            "rotate",
                            class = "actionLinkButton",
                            onclick = "Shiny.setInputValue('refresh', Date.now() , {priority: 'event'})"
                        ),
                    )
                )
            ),
            bslib::nav_panel(
                title = "B",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "B",
                    ),
                    bslib::nav_spacer(),
                    bslib::nav_item(
                        shiny::icon(
                            "rotate",
                            class = "actionLinkButton",
                            onclick = "Shiny.setInputValue('refresh', Date.now() , {priority: 'event'})"
                        ),
                    )
                )
            ),
            bslib::nav_panel(
                title = "C",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "C",
                    ),
                    bslib::nav_spacer(),
                    bslib::nav_item(
                        shiny::icon(
                            "rotate",
                            class = "actionLinkButton",
                            onclick = "Shiny.setInputValue('refresh', Date.now() , {priority: 'event'})"
                        ),
                    )
                )
            ),
            bslib::nav_panel(
                title = "D",
                bslib::navset_card_underline(
                    bslib::nav_panel(
                        title = "D",
                    ),
                    bslib::nav_spacer(),
                    bslib::nav_item(
                        shiny::icon(
                            "rotate",
                            class = "actionLinkButton",
                            onclick = "Shiny.setInputValue('refresh', Date.now() , {priority: 'event'})"
                        ),
                    )
                )
            )
        )
    )
# spacer
bslib::nav_insert(
    id = "main",
    bslib::nav_spacer(),
)

# settings
bslib::nav_insert(
    id = "main",
    bslib::nav_menu(
        title = "Settings",
        icon = bsicons::bs_icon("gear"),
        align = "right",
        bslib::nav_item(
            shiny::actionLink(
                "user_logout",
                "Logout",
                icon = shiny::icon("right-from-bracket")
            )
        )
    )
)

}

  • This behavior does not occur if the nav_menu() is not rendered in the server (just putting it in the UI).
  • It does not occur if the inserted panels are not inside a nav_menu() (nav_insert(id = "main", bslib::nav_panel(title = "A", ...).
  • I'm also confused as the first 2 panels in the menu always render (I would expect only 1), but never any panel afterwards even if the panel does not contain the repeated input (panel D).

As such, this seems like an incredibly niche issue, but did take a good long while to figure out the fix without any helpful errors, etc.

Session Info


─ Session info ──────────────────────────────────────────────
 setting  value
 version  R version 4.4.2 (2024-10-31)
 os       Ubuntu 22.04.5 LTS
 system   x86_64, linux-gnu
 ui       X11
 language (EN)
 collate  C.UTF-8
 ctype    C.UTF-8
 tz       America/Denver
 date     2025-01-08
 pandoc   2.9.2.1 @ /usr/bin/pandoc

─ Packages ──────────────────────────────────────────────────
package * version date (UTC) lib source
bsicons * 0.1.2 2023-11-04 [1] CRAN (R 4.4.2)
bslib * 0.8.0 2024-07-29 [1] CRAN (R 4.4.2)
cachem 1.1.0 2024-05-16 [1] CRAN (R 4.4.2)
cli 3.6.3 2024-06-21 [1] CRAN (R 4.4.2)
devtools 2.4.5 2022-10-11 [1] CRAN (R 4.4.2)
digest 0.6.37 2024-08-19 [1] CRAN (R 4.4.2)
ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.4.2)
fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.4.2)
fs 1.6.5 2024-10-30 [1] CRAN (R 4.4.2)
glue 1.8.0 2024-09-30 [1] CRAN (R 4.4.2)
htmltools 0.5.8.1 2024-04-04 [1] CRAN (R 4.4.2)
htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.4.2)
httpuv 1.6.15 2024-03-26 [1] CRAN (R 4.4.2)
jquerylib 0.1.4 2021-04-26 [1] CRAN (R 4.4.2)
jsonlite 1.8.9 2024-09-20 [1] CRAN (R 4.4.2)
later 1.4.1 2024-11-27 [1] CRAN (R 4.4.2)
lifecycle 1.0.4 2023-11-07 [1] CRAN (R 4.4.2)
magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.4.2)
memoise 2.0.1 2021-11-26 [1] CRAN (R 4.4.2)
mime 0.12 2021-09-28 [1] CRAN (R 4.4.2)
miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.4.2)
pkgbuild 1.4.5 2024-10-28 [1] CRAN (R 4.4.2)
pkgload 1.4.0 2024-06-28 [1] CRAN (R 4.4.2)
profvis 0.4.0 2024-09-20 [1] CRAN (R 4.4.2)
promises 1.3.2 2024-11-28 [1] CRAN (R 4.4.2)
purrr 1.0.2 2023-08-10 [1] CRAN (R 4.4.2)
R6 2.5.1 2021-08-19 [1] CRAN (R 4.4.2)
Rcpp 1.0.13-1 2024-11-02 [1] CRAN (R 4.4.2)
remotes 2.5.0 2024-03-17 [1] CRAN (R 4.4.2)
renv 1.0.11 2024-10-12 [1] CRAN (R 4.4.2)
rlang 1.1.4 2024-06-04 [1] CRAN (R 4.4.2)
sass 0.4.9 2024-03-15 [1] CRAN (R 4.4.2)
sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.4.2)
shiny * 1.10.0 2024-12-14 [1] CRAN (R 4.4.2)
urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.4.2)
usethis 3.1.0 2024-11-26 [1] CRAN (R 4.4.2)
vctrs 0.6.5 2023-12-01 [1] CRAN (R 4.4.2)
xtable 1.8-4 2019-04-21 [1] CRAN (R 4.4.2)

@gadenbuie
Copy link
Member

The repeated shiny::actionLink("refresh") fetches data for that page based on input$main. After updating from shiny 1.9.1, to shiny 1.10.0, this app no longer functions.

The duplicated input ID is definitely the source of the problem. We made a small change in shiny 1.10.0 that should better surface warnings about duplicated or shared input/output IDs.

In general, I'd urge you not to repeat input or output IDs in your apps. Your solution of using Shiny.setInputValue() is spot on and I'd recommend that approach over repeating action buttons. There's a subtle bug that will happen with action buttons that each button will maintain their own counter, so sometimes a user would click "refresh" and nothing will happen if it doesn't change the value of input$refresh.

In terms of the markup, I'd recommend the following:

nav_item(
	tags$a(
		class = "nav-link",
		href = "#",
		onclick = "Shiny.setInputValue('refresh', Date.now() , {priority: 'event'})",
		bsicons::bs_icon(
			"arrow-clockwise",
			size = "1rem",
			title = "Refresh data"
		)
	)
)

Two important changes:

  1. Use tags$a() or tags$button() for the element with the onclick handler. Buttons and links are intended to be clicked and the browser adds features to them to ensure they're accessible to screen readers and keyboard users.
  2. If you're using bsicons, use the title argument to ensure that the icon is given appropriate ARIA labels. With shiny::icon() I think you could add aria-label="Refresh" to the tags$a() element (we need to bring the same ARIA labelling features from bsicons to the fontawesome package).

As such, this seems like an incredibly niche issue, but did take a good long while to figure out the fix without any helpful errors, etc.

Sorry about that! The change was intended to bring clarity to some otherwise obscure issues, but I think something unusual is happening with bslib::nav_insert(). I'll look into it soon, but I'm glad there are workarounds – in fact, using unique IDs or the nav item above are both better than the original code where inputs are repeated.

@gadenbuie gadenbuie changed the title i.get is not a function - new error as of shiny 1.10.0 bslib::nav_insert() with repeated inputs fails with error console changes in shiny 1.10.0 Jan 8, 2025
@gadenbuie
Copy link
Member

Here's a much smaller reprex:

library(shiny)
library(bslib)

ui <- bslib::page_navbar(
	title = "Reprex",
	id = "main",
	lang = "en"
)

action_link <- shiny::actionLink("refresh", "Refresh", class = "nav-link")

server <- function(input, output, session) {
	bslib::nav_insert(
	    id = "main",
	    nav = bslib::nav_menu(
	        title = "Page",
	        bslib::nav_panel(title = "A", "Page A", action_link),
	        bslib::nav_panel(title = "B", "Page B", action_link),
            # Additional nav panels after duplicate ID warning:
            # >> scope.get is not a function
            bslib::nav_panel(title = "D", "Page D") 
	    )
	)

    # This insert works okay
    # bslib::nav_insert(
    #     id = "main",
    #     nav = bslib::nav_panel(title = "D", "Page D")
    # )
}

shiny::shinyApp(ui, server)

It appears that you need to:

  1. Add UI with repeated IDs in a single nav_insert() call
  2. Have more nav_panel() elements inserted in the same call after the duplicate ID is thrown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants