Question: I'm encountering a peculiar issue when developing my Python shiny app. My app currently has the functionality to dynamically generate new tabs with the press of a navset tab called "+". However, after pressing "+", the state (including input and output values) of the previous tabs reset back to empty. Is there a way to preserve the state of any previously existing tabs? My code is outlined below: from shiny import App, Inputs, Outputs, Session, module, reactive, render, ui # Create a Module UI @module.ui def textbox_ui(panelNum): return ui.nav_panel( f"Tab {panelNum}", ui.input_text_area(id=f"test_text", label = "Enter some text"), ui.output_ui(f"display_text_{panelNum}"), value = f"Tab_{panelNum}" ) # Set up module server @module.server def textbox_server(input, output, session, panelNum): @output(id=f"display_text_{panelNum}") @render.text def return_text(): return input[f"test_text"]() # Set up app UI app_ui = ui.page_fluid( ui.tags.head( ui.tags.style( """ body { height: 100vh; overflow-y: auto !important; } """ ) ), ui.h2('Test App', align='center', fillable=True), ui.output_ui("tab_UI"), title = "Test App" ) # Set up server def server(input, output, session): # Set up reactive values navs = reactive.value(0) # Add tabs if the user presses "+" @reactive.effect @reactive.event(input.shiny_tabs) def add_tabs(): if input.shiny_tabs() == "+": navs.set(navs.get() + 1) @output @render.ui def tab_UI(): [textbox_server(str(x), panelNum=x+1) for x in range(navs.get())] ui.update_navs("shiny_tabs", selected = f"Tab_{navs.get()}") return ui.navset_tab( ui.nav_panel("Home", ui.card( ui.card_header("Overview"), ui.p("An example of the outputs clearing") ), value = "panel0" ), *[textbox_ui(str(x), panelNum=x+1) for x in range(navs.get())], ui.nav_panel("+"), id = "shiny_tabs" ) app = App(app_ui, server) 

Answer: The reason for the reset is that the navset_tab is re-rendered each time a new nav_panel gets appended. So an approach would be better where we have the navset_tab outside of the server and then append a nav_panel on click without re-rendering everything. The difficulty is on the one hand that ui.insert_ui does not seem to be suitable for the insert and on the other hand that Shiny for Python currently does not carry functions for dynamic navs, see e.g. posit-dev/py-shiny#089. However, within the PR#90 is a draft for an insert function nav_insert which is suitable for this application. I adapted this below and re-wrote your app, we now only insert a new tab if the button is clicked, the rest stays stable.  import sys from shiny import App, reactive, ui, Session, Inputs, Outputs, module, render from shiny._utils import run_coro_sync from shiny._namespaces import resolve_id from shiny.types import NavSetArg from shiny.session import require_active_session from typing import Optional, Union if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal # adapted from https://github.com/posit-dev/py-shiny/pull/90/files def nav_insert( id: str, nav: Union[NavSetArg, str], target: Optional[str] = None, position: Literal["after", "before"] = "after", select: bool = False, session: Optional[Session] = None, ) -> None: """ Insert a new nav item into a navigation container. Parameters ---------- id The ``id`` of the relevant navigation container (i.e., ``navset_*()`` object). nav The navigation item to insert (typically a :func:`~shiny.ui.nav` or :func:`~shiny.ui.nav_menu`). A :func:`~shiny.ui.nav_menu` isn't allowed when the ``target`` references an :func:`~shiny.ui.nav_menu` (or an item within it). A string is only allowed when the ``target`` references a :func:`~shiny.ui.nav_menu`. target The ``value`` of an existing :func:`shiny.ui.nav` item, next to which tab will be added. Can also be ``None``; see ``position``. position The position of the new nav item relative to the target nav item. If ``target=None``, then ``"before"`` means the new nav item should be inserted at the head of the navlist, and ``"after"`` is the end. select Whether the nav item should be selected upon insertion. session A :class:`~shiny.Session` instance. If not provided, it is inferred via :func:`~shiny.session.get_current_session`. """ session = require_active_session(session) li_tag, div_tag = nav.resolve( selected=None, context=dict(tabsetid="tsid", index="id") ) msg = { "inputId": resolve_id(id), "liTag": session._process_ui(li_tag), "divTag": session._process_ui(div_tag), "menuName": None, "target": target, "position": position, "select": select, } def callback() -> None: run_coro_sync(session._send_message({"shiny-insert-tab": msg})) session.on_flush(callback, once=True) @module.ui def textbox_ui(panelNum): return ui.nav_panel( f"Tab {panelNum}", ui.input_text_area(id=f"test_text_{panelNum}", label = "Enter some text"), ui.output_ui(f"display_text_{panelNum}"), value = f"Tab_{panelNum}" ) @module.server def textbox_server(input, output, session, panelNum): @output(id=f"display_text_{panelNum}") @render.text def return_text(): return input[f"test_text_{panelNum}"]() app_ui = ui.page_fluid( ui.tags.head( ui.tags.style( """ body { height: 100vh; overflow-y: auto !important; } """ ) ), ui.h2('Test App', align='center', fillable=True), ui.navset_tab( ui.nav_panel("Home", ui.card( ui.card_header("Overview"), ui.p("An example of the outputs not clearing") ), value = "Tab_0" ), ui.nav_panel("+"), id = "shiny_tabs" ), title = "Test App" ) def server(input: Inputs, output: Outputs, session: Session): navCounter = reactive.value(0) @reactive.effect @reactive.event(input.shiny_tabs) def add_tabs(): if input.shiny_tabs() == "+": navCounter.set(navCounter.get() + 1) id = str(navCounter.get()) idPrev = str(navCounter.get() - 1) nav_insert( "shiny_tabs", textbox_ui(id, id), target=f"Tab_{idPrev}", position="after", select=True ) textbox_server(id, id) app = App(app_ui, server)