Tutorial: Using reactRouter with rhino and shiny.fluent

Introduction

This tutorial demonstrates how to build a dynamic Shiny application using reactRouter for routing and shiny.fluent for modern UI components, all within the rhino framework. We’ll use Dota 2 API data as an example of routing multiple pages.

Initially, ensure you have the necessary rhino package installed. You can do this by running the following command in your R console:

# Install rhino if not yet installed
install.packages("rhino")

Next, you will need to create a new rhino project. If you haven’t already set up a rhino project, you can do so by running the following command in your R console:

# Initialize a new rhino project (will create project scaffolding)
rhino::init()

This will create a basic structure for your application. Add the following libraries to your dependencies.R file:

# dependencies.R

library(rhino) # App structure
library(httr) # API requests
library(shiny.fluent) # UI components
library(reactRouter) # Client-side routing
library(echarts4r) # Charting
library(stringdist) # String matching
library(treesitter) # Optional: Syntax parsing
library(treesitter.r) # Optional: R syntax support

and then

renv::snapshot()

Now we are ready to go.

Building the Application

In this example, we will create a simple application that displays information about Dota 2 heroes. The application will have multiple routes, allowing users to navigate between different pages.

The components of the application will be structured as follows:

The final strucutre of the app will look like this:

├── app
│   ├── js
│   │   └── index.js
│   ├── logic
│   │   ├── data.R
│   │   └── utils.R
│   ├── main.R
│   ├── static
│   │   ├── css
│   │   │   └── app.min.css
│   │   ├── favicon.ico
│   │   └── js
│   │       └── app.min.js
│   ├── styles
│   │   └── main.scss
│   └── view
│       ├── benchmark.R
│       ├── details.R
│       ├── header.R
│       ├── home.R
│       ├── menu.R
│       └── rank.R
├── app.R
├── config.yml
├── dependencies.R
├── renv.lock
├── rhino.yml
└── run_dev.R

The main part of the application to address the routing and the UI components is in the app.R file.

# app / main.R

box::use(
  app / view / home,
  app / view / menu,
  app / view / details,
  app / view / benchmark,
  app / view / rank
)


# Define UI with namespaced modules
ui <- function(id) {
  ns <- shiny::NS(id) # Namespace for module isolation
  shiny.fluent::fluentPage(
    reactRouter::HashRouter(
      reactRouter::Routes(
        # Home page route
        reactRouter::Route(path = "/", element = home$ui(ns("home"))),

        # Project-based nested routes
        reactRouter::Route(
          path = "/:projectId/*",
          element = menu$ui(ns("menu")), # Common layout/menu
          children = list(
            reactRouter::Route(
              path = "details",
              element = details$ui(ns("details"))
            ),
            reactRouter::Route(
              path = "benchmark",
              element = benchmark$ui(ns("benchmark"))
            ),
            reactRouter::Route(
              path = "rank",
              element = rank$ui(ns("rank"))
            )
          )
        ),

        # Fallback for undefined routes
        reactRouter::Route(path = "*", element = "Custom error 404")
      )
    )
  )
}

#' @export
server <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    hero_selected <- home$server("home")

    shiny::observe({
      shiny::req(hero_selected())

      print(paste0("hero_id selected: ", hero_selected()))
    })

    menu$server("menu", hero_selected = hero_selected)
    details$server("details", hero_selected = hero_selected)
    benchmark$server("benchmark", hero_selected = hero_selected)
    rank$server("rank", hero_selected = hero_selected)
  })
}

This function defines the overall layout and routing of the application using reactRouter. It contains four key parts:

  1. Top-level routing via HashRouter() and Routes().

  2. Root Route /: Displays the home page.

  3. Nested Route /:projectId/*:

  4. Displays a layout (menu) and child routes (details, benchmark, rank).

  5. Each sub-route renders a different module UI (e.g., details\(ui, benchmark\)ui).

  6. Fallback * Route: Catches any undefined paths and shows a custom 404 message.

Running the Application

You can now run your app locally with the following script:

# run_dev.R

rhino::build_js()
rhino::build_sass()
shiny::runApp(port = 4929, launch.browser = FALSE)

Recap

In this tutorial, dynamic Shiny application using:

This setup provides flexibility, scalability, and maintainability for more complex Shiny applications.