
Tutorial: Using reactRouter with rhino and shiny.fluent
Source:vignettes/shiny.fluent.Rmd
shiny.fluent.RmdIntroduction
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 supportand 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:
- home: the main page of the application, which will display a list of heroes
- menu: a navigation menu for the application, allowing users to navigate between different pages
- header: a header component that will be displayed on every page
- details: a page containing detailed information about a specific hero
- benchmark: a page that will display benchmark statistics for heroes
- ranks: a page that will display the ranks of heroes based on their performance
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.RThe 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:
Top-level routing via
HashRouter()andRoutes().Root Route /: Displays the home page.
Nested Route /:projectId/*:
Displays a layout (menu) and child routes (details, benchmark, rank).
Each sub-route renders a different module UI (e.g., detailsui).
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)