Introduction to reactRouter

Install

#remotes::install_github("lgnbhl/reactRouter") # development version

install.packages("reactRouter")

Minimal example

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")
  )
)

Usage with shiny

A minimal example using shiny.

library(shiny)
library(reactRouter)

ui <- HashRouter(
  NavLink(to = "/", "Main"), 
  shiny::br(),
  NavLink(to = "/other", "Other"),
  Routes(
    Route(
      path = "/", 
      element = uiOutput(outputId = "uiMain")
    ),
    Route(
      path = "/other", 
      element = uiOutput(outputId = "uiOther")
    )
  )
)

server <- function(input, output, session) {
  output$uiMain <- renderUI( { p("Content home") } )
  output$uiOther <- renderUI( { p("Other content") })
}

shinyApp(ui = ui, server = server)

Usage with bslib

A minimal example using bslib.

library(reactRouter)
library(bslib)
library(htmltools)

reactRouter::HashRouter(
  bslib::page_navbar(
    title = "reactRouter with bslib",
    nav_item(
      reactRouter::NavLink(
        "Home", 
        to = "/"
      )
    ),
    nav_item(
      reactRouter::NavLink(
        "Analysis", 
        to = "/analysis"
      )
    ),
    reactRouter::Routes(
      reactRouter::Route(
        path = "/",
        element = div(
          tags$h3("Home page"),
          p("A basic example of reactRouter with bslib.")
        )
      ),
      reactRouter::Route(
        path = "/analysis",
        element = "Content analysis"
      ),
      reactRouter::Route(path = "*", element = "Custom error 404")
    )
  )
)

Usage with shinyMaterialUI

A minimal example using shinyMaterialUI.

# remotes::install_github("lgnbhl/shinyMaterialUI")
library(shinyMaterialUI)

HashRouter(
  Box(
    sx = list(flexGrow = 1),
    AppBar(
      position = "static",
      Toolbar(
        Typography(
          variant = "h6",
          component = "div",
          sx = list(mr = 1),
          "shinyMaterialUI"
        ),
        NavLink(
          to = "/",
          Button(
            color = "inherit",
            "Home"
          )
        ),
        NavLink(
          to = "analysis",
          Button(
            color = "inherit",
            "Analysis"
          )
        )
      )
    ),
      Box(
        reactRouter::Routes(
          reactRouter::Route(
            path = "/",
            element = Box("Home page", sx = list(p = 1))
          ),
          reactRouter::Route(
            path = "/analysis",
            element = Box("Content analysis", sx = list(p = 1))
          ),
          reactRouter::Route(path = "*", element = "Error 404")
        )
      )
  )
)

Find more examples with shinyMaterialUI here.

Usage with Shiny modules

# 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("Other page", "Other 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(to = "/", "Main"), br(),
  NavLink(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) {
  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)

Example with Quarto

As React Router provides client routing, you can easily create multiple routes in a Quarto or 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(
    style = "border:1px solid black;", # add border just for the example
    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")
            )
          )
        )
      )
    )
  )
)

Dynamic segments

A minimal example using dynamic segments, i.e. using Route(to = ":id/*").

library(shiny)
library(reactRouter)
library(bslib)

ui <- HashRouter(
  bslib::page(
    Link(
      to = "/", 
      h3("reactRouter with dynamic routes", class = "m-3"),
      style = "text-decoration: none; color: black"
    ),
    Routes(
      Route(
        path = "/",
        element = div(
          # tags$a() necessary to observe `url_hash` in session
          NavLink(
            to = "project/1/overview",
            "Project 1"
          ),
          tags$br(),
          NavLink(
            to = "project/2/overview",
            "Project 2"
          )
        )
      ),
      Route(
        path = "project/:id/*", 
        element = div(
          NavLink(
            to = "overview",
            "Overview"
          ),
          tags$br(),
          NavLink(
            to = "analysis",
            "Analysis"
          ),
          Outlet()
        ),
        children = list(
          reactRouter::Route(
            path = "overview",
            element = uiOutput("uiOverview")
          ),
          reactRouter::Route(
            path = "analysis",
            element = uiOutput("uiAnalysis")
          )
        )
      )
    )
  )
)

server <- function(input, output, session) {
  
  url_hash <- shiny::reactiveVal(value = NA)

  # update reactive values based on url hash
  observeEvent(session$clientData$url_hash, {
    current_url_hash <- session$clientData$url_hash
    print(current_url_hash)
    url_hash(current_url_hash)
  })
  
  output$uiOverview <- renderUI({
    url_hash()
  })
  output$uiAnalysis <- renderUI({
    url_hash()
  })
}

shinyApp(ui, server)

Run a more advanced example of dynamic routes with:

reactRouterExample("dynamic-segments")

Alternatives

More information

reactRouter implements React Router v.6.30.0.

More info about how to use React Router can be found in the official website.