R
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Data Structures
-
Section 4: Data Manipulation
-
Section 5: Visualization and Statistics
-
Section 6: Advanced R
-
Section 7: Practical Projects
-
Section 8: Interview Practice
-
Section 9: More Practice Exercises
-
Section 10: Shiny Apps in Depth
-
Section 11: More Data Wrangling
-
Section 12: Tidyverse Deep Dive
-
Section 13: Statistical Modeling Deep Dive
-
Section 14: Machine Learning in R
-
Section 15: R Visualization Deep Dive
-
Section 16: R Package Development Deep Dive
-
Section 17: R for Reproducible Research
-
Section 18: R and Databases
-
Section 19: R Performance Optimization
-
Section 20: Bioinformatics and Specialized R
-
Section 21: More Shiny Practice
-
Section 22: More Practice Exercises
-
Section 23: R Data Structures Deep Dive
-
Section 24: More Interview and Review
-
Section 25: R for Business Analytics
-
Section 26: R Text Mining and NLP
-
Section 27: R Spatial Data Analysis
-
Section 28: R Deep Learning
-
Section 29: Advanced Statistical Techniques
-
Section 30: R Object Systems Deep Dive
-
Section 31: R Environments and Metaprogramming
-
Section 32: R for Finance
-
Section 33: R for Clinical and Health Data
-
Section 34: More Shiny Advanced Practice
-
Section 35: R Data Cleaning Deep Dive
-
Section 36: R Reporting Automation
-
Section 37: More Practical Projects Round 2
-
Section 38: R Ecosystem and Career
-
Section 39: More Visualization Practice
-
Section 40: R for Experimentation
-
Section 41: R for Genomics and Bioinformatics Deep Dive
-
Section 42: R for Marketing Analytics
-
Section 43: R Data Import/Export Deep Dive
-
Section 44: R String Processing Deep Dive
-
Section 45: R for Actuarial and Insurance Analytics
-
Section 46: R Testing and Quality Assurance Deep Dive
-
Section 47: R Version Control and Collaboration
-
Section 48: R Advanced Functional Programming
-
Section 49: R for Supply Chain and Operations
-
Section 50: More Practice Exercises Round 3
-
Section 51: R Dashboards and BI Integration
-
Section 52: R Data Governance and Ethics
-
Section 53: More Modeling Practice
-
Section 54: R Final Capstone Projects
-
Section 55: R for Sports Analytics
-
Section 56: More Interview Practice Round 2
-
Section 57: R Networking and APIs
-
Section 58: R for Environmental Science
-
Section 59: R Notebook and Documentation Practices
-
Section 60: More Data Wrangling Mastery
-
Section 61: R for A/B Testing at Scale
-
Section 62: R Package Ecosystem Deep Dive
44: Building Multi-Page Shiny Apps
When you first realize your Shiny app needs more than one screen, the instinct is usually to treat the UI like a slide deck. I've seen this a dozen times: a developer adds a selectInput or a few action buttons to act as a "menu," then wraps every single piece of the UI in conditionalPanel or a massive uiOutput. It feels intuitive because you're essentially just telling Shiny, "If the user clicked Page B, hide Page A and show this other stuff."
The "Toggling" Trap
The problem with this approach isn't that it doesn't work—it's that it scales terribly. Imagine you're building a Corporate Finance Dashboard. You have a "Revenue Overview" page and a "Detailed Expenses" page. Using conditionalPanel, your ui.R becomes a monolithic wall of code. You're scrolling through five hundred lines of nested DIVs just to find a single plot's output ID.
But the real nightmare happens in the server logic. Since everything is technically "active" even if it's hidden from the user, your server function becomes a giant tangle of observeEvent and reactive blocks. You'll find yourself writing guards like if (input$page == "Expenses") { ... } inside every single reactive expression just to prevent the app from trying to calculate expense totals while the user is still looking at the revenue page. It's fragile, it's hard to test, and one typo in a string like "Expenses" can silently break a whole section of your app.
# The naive way: Everything in one giant bucket
ui <- fluidPage(
selectInput("page", "Navigate", choices = c("Overview", "Details")),
conditionalPanel(
condition = "input.page == 'Overview'",
plotOutput("overview_plot")
),
conditionalPanel(
condition = "input.page == 'Details'",
tableOutput("details_table")
)
)
Decoupling with navbarPage and Modules
The professional way to handle this is to stop thinking about "toggling visibility" and start thinking about "isolated components." For most apps, navbarPage is the structural answer, but the secret sauce is combining it with Shiny Modules. Instead of one giant server function, you create a separate server function for each page.
By wrapping each page in a module, you create a namespace. This means your "Overview" page can have an output called plot and your "Details" page can also have an output called plot without them colliding. You aren't just organizing your code; you're creating a boundary. If the "Details" page crashes because of a data type error, it's much easier to isolate the bug when that page's logic is encapsulated in its own server_details function rather than buried in a 1,000-line file.
# The professional way: Modularized pages
# In a separate file: modules_overview.R
overviewUI <- function(id) {
ns <- NS(id)
tagList(plotOutput(ns("plot")))
}
overviewServer <- function(id) {
moduleServer(id, function(input, output, session) {
output$plot <- renderPlot({ ... })
})
}
# In app.R
ui <- navbarPage("Finance Dashboard",
tabPanel("Overview", overviewUI("overview")),
tabPanel("Details", detailsUI("details"))
)
server <- function(input, output, session) {
overviewServer("overview")
detailsServer("details")
}
The Cost of the Shift
I'll be honest: this adds a bit of boilerplate. You have to deal with NS(id) and the overhead of calling moduleServer. If you're building a tiny internal tool with two screens, this is probably overkill. But the moment your app is intended for production or will be maintained by a team, the trade-off is a no-brainer. You trade a few extra lines of setup for the ability to work on the "Details" page without worrying that you're accidentally breaking the "Overview" page. You're moving from a script that happens to be an app to a software architecture.
📋 Practical Task
Build a Modular Environmental Monitoring Dashboard
You are tasked with upgrading a messy, single-file environmental app into a professional multi-page structure. The app needs two distinct sections: Air Quality and Water Quality.
Your goal is to implement the following requirements:
- Use
navbarPageto create the main navigation. - Create two separate Shiny Modules (one for Air Quality, one for Water Quality).
- Each module must contain its own
UIfunction andServerfunction. - Inside the Air Quality module, include a
plotOutputthat displays a random line chart. - Inside the Water Quality module, include a
tableOutputthat displays a small data frame of "pH levels" and "Turbidity". - Ensure that both modules use their own namespaces (
NS) so that the IDs do not conflict.
Constraint: Do not use conditionalPanel or uiOutput for the page switching; the navigation must be handled by the navbarPage architecture.
There are no comments for now.