knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
#remotes::install_github("lgnbhl/reactRouter") # development version install.packages("reactRouter")
library(reactRouter)
You can add URL pages in Quarto document or R shiny like so:
library(reactRouter) HashRouter( NavLink(to = "/", "Main"), NavLink(to = "/analysis", "Analysis"), Routes( Route(path = "/", element = "Main content"), Route(path = "/analysis", element = "Analysis content") ) )
As React Router provides client routing, you can easily create multiple routes in a Quarto and R markdown documents:
# code to run in a Quarto document # example adapted from: https://github.com/remix-run/react-router/tree/dev/examples/basic library(reactRouter) library(htmltools) Layout <- div( # A "layout route" is a good place to put markup you want to # share across all the pages on your site, like navigation. tags$nav( tags$ul( tags$li( reactRouter::Link(to = "/", "Home") ), tags$li( reactRouter::Link(to = "/dashboard", "Dashboard") ), tags$li( reactRouter::Link(to = "/nothing-here", "Nothing Here") ) ) ), tags$hr(), # An <Outlet> renders whatever child route is currently active, # so you can think about this <Outlet> as a placeholder for # the child routes we defined above. reactRouter::Outlet() ) reactRouter::HashRouter( div( h1("Basic Example"), tags$p( paste0('This example demonstrates some of the core features of React Router including nested reactRouter::Route(), reactRouter::Outlet(), reactRouter::Link(), and using a "*" route (aka "splat route") to render a "not found" page when someone visits an unrecognized URL.' ) ), reactRouter::Routes( Route( path = "/", element = Layout, Route( index = TRUE, element = div( tags$h2("Home"), tags$p("Home content") ) ), Route( path = "dashboard", element = div( tags$h2("Dashboard"), tags$p("Dashboard here") ) ), # Using path="*"" means "match anything", so this route # acts like a catch-all for URLs that we don't have explicit # routes for. Route( path = "*", element = div( tags$h2("Nothing to see here!"), tags$p( Link(to = "/", "Go to the home page") ) ) ) ) ) ) )
Below a simple example using R Shiny:
library(reactRouter) library(shiny) library(DT) ui <- fluidPage( reactRouter::HashRouter( titlePanel("reactRouter"), sidebarLayout( sidebarPanel( reactRouter::NavLink( to = "/", style = JS('({isActive}) => { return isActive ? {color: "red", textDecoration:"none"} : {}; }'), "Home"), br(), reactRouter::NavLink( to = "/analysis", style = JS('({isActive}) => { return isActive ? {color: "red", textDecoration: "none"} : {}; }'), "Analysis" ), br(), reactRouter::NavLink( to = "/about", style = JS('({ isActive }) => { return isActive ? { color: "red", textDecoration: "none" } : {}; }'), "About" ) ), mainPanel( reactRouter::Routes( reactRouter::Route( path = "/", element = div( tags$h1("Home page"), tags$h4("A basic example of reactRouter."), uiOutput(outputId = "contentHome") ) ), reactRouter::Route( path = "/analysis", element = div( tags$h1("Analysis"), DT::DTOutput("table") ) ), reactRouter::Route( path = "/about", element = div( uiOutput(outputId = "contentAbout") ) ), reactRouter::Route(path = "*", element = div(tags$p("Error 404"))) ) ) ) ) ) server <- function(input, output, session) { output$contentHome <- renderUI({ p("Content home") }) output$table <- renderDT({ data.frame(x = c(1, 2), y = c(3, 4)) }) output$contentAbout <- renderUI({ div( tags$h1("About"), p("Content about") ) }) } shinyApp(ui, server)
Below an example using MUI Material UI components from the shinyMaterialUI R package:
# remotes::install_github("lgnbhl/shinyMaterialUI") library(shiny) library(shinyMaterialUI) library(reactRouter) ui <- reactRouter::HashRouter( CssBaseline( Typography("reactRouter with shinyMaterialUI", variant = "h5", m = 2), Stack( direction = "row", spacing = 2, p = 2, Paper( MenuList( reactRouter::NavLink( to = "/", style = JS('({isActive}) => { return isActive ? {color: "red", textDecoration:"none"} : { textDecoration: "none" }; }'), MenuItem( "home" ) ), br(), reactRouter::NavLink( to = "/analysis", style = JS('({isActive}) => { return isActive ? {color: "red", textDecoration: "none"} : { textDecoration: "none" }; }'), MenuItem( "Analysis" ) ), br(), reactRouter::NavLink( to = "/about", style = JS('({ isActive }) => { return isActive ? { color: "red", textDecoration: "none" } : { textDecoration: "none" }; }'), MenuItem( "About" ) ) ) ), Box( reactRouter::Routes( reactRouter::Route( path = "/", element = div( tags$h1("Home page"), tags$h4("A basic example of reactRouter with shinyMaterialUI."), uiOutput(outputId = "contentHome") ) ), reactRouter::Route( path = "/analysis", element = div( tags$h1("Analysis"), uiOutput(outputId = "contentAnalysis") ) ), reactRouter::Route( path = "/about", element = uiOutput(outputId = "contentAbout") ), reactRouter::Route(path = "*", element = div(tags$p("Error 404"))) ) ) ) ) ) server <- function(input, output, session) { output$contentHome <- renderUI({ p("Content home") }) output$contentAnalysis <- renderUI({ p("Content analysis") }) output$contentAbout <- renderUI({ p("Content about") }) } shinyApp(ui, server)
If some pages have the same HTML structure, shiny will not update the content rendered in the server when clicking on a new page.
Here an minimal example of the issue:
library(shiny) library(reactRouter) ui <- HashRouter( NavLink.shinyInput(inputId = "linkMain", to = "/", "Main"), br(), NavLink.shinyInput(inputId = "linkOther", to = "/other", "Other"), Routes( Route( path = "/", element = div(uiOutput(outputId = "contentMain")) ), Route( path = "/other", element = div(uiOutput(outputId = "contentOther")) ) ) ) server <- function(input, output, session) { output$contentMain <- renderUI( { p("Content home") } ) output$contentOther <- renderUI( { p("New content") }) } if (interactive()) { shinyApp(ui = ui, server = server) }
A simple workaround is to have a different HTML structure for each page.
library(shiny) library(reactRouter) ui <- HashRouter( NavLink.shinyInput(inputId = "linkMain", to = "/", "Main"), br(), NavLink.shinyInput(inputId = "linkOther", to = "/other", "Other"), Routes( Route( path = "/", element = div(uiOutput(outputId = "contentMain")) ), Route( path = "/other", element = div( # add title to have a different HTML structure h1("NOW VISIBLE"), uiOutput(outputId = "contentOther") ) ) ) ) server <- function(input, output, session) { output$contentMain <- renderUI( { p("Content home") } ) output$contentOther <- renderUI( { p("New content") }) } if (interactive()) { shinyApp(ui = ui, server = server) }
Another workaround is to reload the session when clicking on a page link using NavLink.shinyInput()
instead of NavLink()
. This is equivalent to clicking on the refresh button of your browser.
This workaround has multiple downsides, in particular loose all current session objects when reloading the session.
library(shiny) library(reactRouter) ui <- HashRouter( NavLink.shinyInput(inputId = "linkMain", to = "/", "Main"), br(), NavLink.shinyInput(inputId = "linkOther", to = "/other", "Other"), Routes( Route( path = "/", element = div(uiOutput(outputId = "contentMain")) ), Route( path = "/other", element = div( uiOutput(outputId = "contentOther") ) ) ) ) server <- function(input, output, session) { # Reload session when user clicks on new page to refresh output content. # It is equivalent to click on the refresh button in the browser. observeEvent(c(input$linkMain, input$linkOther), { session$reload() }) output$contentMain <- renderUI( { p("Content home") } ) output$contentOther <- renderUI( { p("New content") }) } if (interactive()) { shinyApp(ui = ui, server = server) }
A third workaround is to render all the UI from the server.
library(shiny) library(reactRouter) ui <- uiOutput(outputId = "main") server <- function(input, output, session) { output$main <- renderUI({ HashRouter( NavLink.shinyInput(inputId = "linkMain", to = "/", "Main"), br(), NavLink.shinyInput(inputId = "linkOther", to = "/other", "Other"), Routes( Route( path = "/", element = p("Content home") ), Route( path = "/other", element = div( p("New content"), p(print(JS("location.reload()"))) ) ) ) ) }) } if (interactive()) { shinyApp(ui = ui, server = server) }
More examples of this issue are given here. Please contact me if you have a solution to this issue.
# adapted from example of shiny.router # https://github.com/Appsilon/shiny.router/tree/main/examples/shiny_modules library(shiny) library(reactRouter) # This creates UI for each page. page <- function(title, content, id) { ns <- NS(id) div( titlePanel(title), p(content), textOutput(ns("click_me")) ) } # Both sample pages. root_page <- page("Home page", "Home page clicks", "root") second_page <- page("Second page", "2nd page clicks", "second") server_module <- function(id, clicks, power = 1) { moduleServer(id, function(input, output, session) { output$click_me <- renderText({ as.numeric(clicks())^power }) }) } # Create output for our router in main UI of Shiny app. ui <- reactRouter::HashRouter( NavLink.shinyInput(inputId = "linkMain", to = "/", "Main"), br(), NavLink.shinyInput(inputId = "linkOther", to = "/other", "Other"), actionButton("clicks", "Click me!"), Routes( Route( path = "/", element = div( root_page ) ), Route( path = "/other", element = div( second_page ) ) ) ) # Plug router into Shiny server. server <- function(input, output, session) { # reload the session to make the modules work # Not desired behavior as clicks object lost observeEvent(c(input$linkMain, input$linkOther), { session$reload() }) clicks <- reactive({ input$clicks }) server_module("root", clicks = clicks, power = 1) server_module("second", clicks = clicks, power = 2) } # Run server in a standard way. shinyApp(ui, server)
reactRouter implements React Router v.6.30.0.
More info about how to use React Router can be found in the official website.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.