Skip to main content

ReactApp

ReactApp(
    server,
    *,
    ui=None,
    static_assets=MISSING,
    bookmark_store='disable',
    shinyreact_js='server',
    **kwargs,
)

:class:shiny.App for the ui.tsx pattern — the UI is discovered.

The app file is just the server::

from shinyreact import ReactApp

app = ReactApp(server, bookmark_store="url")

With no ui=, the UI is discovered next to the calling module the same way :func:set_react_page discovers it: www/index.html present → :func:page_react_html; otherwise → :func:page_react (www/ui.js / www/ui.css, served by the dependency itself). The discovered UI is a function of the request, so it re-renders per request — which is what makes bookmark_store= work with no further wiring, and what lets the mode switch mid-session when you create or delete www/index.html.

ui= overrides discovery and behaves exactly like shiny.App’s ui argument, except that a document from :func:page_react_html still gets its directory mounted. Note the discovery reads the immediate calling frame (like :func:page_react_dep), so a helper that wraps ReactApp(...) must pass ui= explicitly.

Args: server: The server function, as for :class:shiny.App. ui: Overrides UI discovery. Anything shiny.App accepts. static_assets: Extra static mounts. Added to the React asset directory rather than replacing it — see below. Defaults to :data:shiny.types.MISSING, not None, so that passing None explicitly can mean “no static assets at all”. bookmark_store: "url" / "server" to enable bookmarking, as for :class:shiny.App. Requires a callable UI, which discovery provides; a static ui=page_react_html(...) raises. shinyreact_js: Who supplies shinyreact.js / shinyreact.css to the discovered UI: "server" (default) or "client" for an npm-tier app whose bundle imports @posit-dev/shinyreact. Ignored when ui= is passed — build that UI with shinyreact_js="client" yourself. **kwargs: Forwarded to :class:shiny.App (debug=, test_mode=).

Static assets

A full HTML document references its bundle with a plain relative URL (<script src="ui.js">), so something has to serve the directory the document lives in. ReactApp mounts it at / for you:

  • discovery modewww/ next to the app file, mounted whenever the directory exists. It is mounted even in page_react() mode, where the dependency serves the assets itself and the mount is unused: the mode is decided per request while static_assets is a constructor argument, so an unused mount is the only way to keep both modes working. An unused mount is harmless; a missing one is a 404 per asset.
  • ui=page_react_html(...) — the document’s own directory, which :func:page_react_html tagged onto the document it returned.

Your static_assets is merged with that mount, not substituted for it, so adding an unrelated mount doesn’t take the bundle down with it::

ReactApp(server, static_assets={"/data": DATA_DIR})
# serves /data from DATA_DIR *and* / from www/

You win wherever you actually collide with it, and there are three ways to::

ReactApp(server, static_assets={"/": DIST_DIR})  # explicit "/" key
ReactApp(server, static_assets=DIST_DIR)         # a bare path *is* "/"
ReactApp(server, static_assets=None)             # nothing mounted

The last one is why the default is :data:shiny.types.MISSING rather than None: with None as the default there would be no way to say “mount nothing”, since not passing the argument and passing None would look identical from in here.

Shiny requires every mount path to be absolute, and sorts mount points by descending length, so a nested mount is matched before /.