I have a shiny app that dynamically add tabs to a tabset panel. When trying to use boookmarking in order to save the state and then restore it, I find that only the last added tab is being bookmarked.
I have tried using both bookmarking to the server as well as to URL. In both cases only the initial tabs (not added dynamically) plus the last dynamically added tab was bookmarked. All other dynamicalled added tabs were not bookmarked and thus did not appear when I restored the state.
Here is a reproducible code:
library(shiny)
# example app for bookmarking and restoring dynamically added tabs
# only the last added tab is bookmarked.
ui <- function(request) {
fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("add", "Add 'Dynamic' tab"),
actionButton("remove", "Remove selected tab"),
# Add a bookmarking buttons
bookmarkButton(),
actionButton("restore", "Restore State")
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Hello", "This is the hello tab")
)
)
)
)
}
server <- function(input, output, session) {
observeEvent(input$add, {
insertTab(inputId = "tabs",
tabPanel(paste("Dynamic",input$add), "This a dynamically-added tab")
)
})
observeEvent(input$remove, {
removeTab(inputId = "tabs", target = input$tabs)
})
onBookmarked(function(url) {
updateQueryString(url)
})
observeEvent(input$restore, {
session$reload()
})
}
enableBookmarking(store = "url")
shinyApp(ui, server)
Well, this behaviour is by design. You see that your bookmark url contains the value of input$add:

That is the value of input$add (3 in this case) is stored. Upon restore you set input$add to three, this fires your observer, which adds one dynamic panel (labeled Dynamic 3).
You need to store all added tabs and supply a "smarter" bookmark (restore) routine like this:
library(shiny)
library(tibble)
library(dplyr)
library(purrr)
ui <- function(request) {
fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("add", "Add 'Dynamic' tab"),
actionButton("remove", "Remove selected tab"),
# Add a bookmarking buttons
bookmarkButton(),
actionButton("restore", "Restore State")
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Hello", "This is the hello tab")
)
)
)
)
}
server <- function(input, output, session) {
dyn_tabs <- reactiveVal(NULL)
added_tabs <- reactiveVal(NULL)
cnt <- reactiveVal(0L)
observeEvent(input$add, {
cnt(cnt() + 1)
id <- paste("Dynamic", cnt())
dyn_tabs(c(dyn_tabs(), id))
})
observeEvent(input$remove, {
remove_me <- input$tabs
dyn_tabs(setdiff(dyn_tabs(), remove_me))
added_tabs(setdiff(added_tabs(), remove_me))
removeTab(inputId = "tabs", target = remove_me)
})
observeEvent(dyn_tabs(), {
new_tabs <- setdiff(dyn_tabs(), added_tabs())
walk(new_tabs, ~ insertTab(inputId = "tabs",
tabPanel(.x, "This is a dynamically added tab")))
added_tabs(union(added_tabs(), new_tabs))
})
setBookmarkExclude(c("add", "delete"))
onBookmark(function(state) {
state$values$dyn_tabs <- dyn_tabs()
state$values$cnt <- cnt()
})
onBookmarked(function(url) {
updateQueryString(url)
})
onRestored(function(state) {
dyn_tabs(state$values$dyn_tabs)
cnt(state$values$cnt)
})
observeEvent(input$restore, {
session$reload()
})
}
enableBookmarking(store = "url")
shinyApp(ui, server)
So basically we keep track about dynamic (but yet not added) panels (dyn_tabs) and save this vector upon bookmark. We keep also a list of added panels (added_tabs) in order not to re-render all panels upon change.
When we restore, we refill these vectors accordingly. We exclude input$add and input$remove from the bookmarking to avoid that it adds (removes) its element upon restore.
However, we keep track about the number of previous clicks (cnt) in order to get a proper labeling.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With