The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

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.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.