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
87: Shiny Modules for Reusable Components
When I first started building complex Shiny apps, I fell into a trap that almost every R developer hits: I thought Shiny Modules were just a fancy way to organize my code. I treated them like folders for my functions—a way to stop my app.R file from becoming a 2,000-line monster. If that's how you're thinking about them, you're missing the entire point of the feature.
Modules aren't for organization; they're for namespace isolation
Here is the concrete problem: Imagine you've built a perfect "Data Filter" UI block with a date range picker and a dropdown for categories. It works great. Now, your boss asks you to add a second, identical filter block for a different dataset on the same page.
If you just copy-paste your UI and server code, your app will break. Why? Because you'll have two inputs both named input$date_range. Shiny has no way of knowing which one you're referring to in your server logic. You'd be forced to manually rename every single ID to input$date_range_1 and input$date_range_2. It's tedious, error-prone, and a total nightmare to maintain.
Modules solve this by creating a "namespace." A module is a self-contained unit that wraps its IDs. When you call a module multiple times, Shiny automatically prefixes the IDs behind the scenes, so date_range becomes filter1-date_range and filter2-date_range without you ever having to type those prefixes manually.
The Pattern: UI and Server Pairs
To make this work, a module consists of two parts: a UI function and a server function. I always recommend naming them with a consistent suffix like UI and Server to keep your sanity.
# The UI part of the module
plotModuleUI <- function(id) {
ns <- NS(id) # This is the magic sauce
tagList(
selectInput(ns("variable"), "Choose Variable", choices = c("mpg", "hp", "wt")),
plotOutput(ns("distPlot"))
)
}
# The Server part of the module
plotModuleServer <- function(id) {
moduleServer(id, function(input, output, session) {
output$distPlot <- renderPlot({
hist(mtcars[[input$variable]], main = "Module Plot")
})
})
}
Notice the ns() function in the UI. That stands for "namespace." Every single ID inside the module's UI must be wrapped in ns(). If you forget one, that specific input will look for its value in the global app scope instead of the module scope, and you'll spend an hour wondering why your reactive values are NULL. I've been there; don't be there.
Plugging Modules into the Main App
Now that we have our module, we can call it as many times as we want in the main app. We just provide a unique string as the id for each instance.
ui <- fluidPage(
plotModuleUI("plot_a"),
plotModuleUI("plot_b")
)
server <- function(input, output, session) {
# We call the server logic for each instance
plotModuleServer("plot_a")
plotModuleServer("plot_b")
}
In this setup, plot_a and plot_b are completely independent. Changing the dropdown in the first plot won't affect the second. You've essentially created a reusable "component" that you can drop into any app you build from now on.
Passing Data Between Modules and the Main App
You'll eventually realize that modules shouldn't be totally isolated; they often need to talk to the main app. The best way to handle this is to have the module's server function return a reactive value.
If my plotModuleServer returned return(reactive(input$variable)), the main app could then capture that value: selected_var <- plotModuleServer("plot_a"). This keeps the module generic while allowing the main app to coordinate the high-level logic.
📋 Practical Task
Exercise: Building a Reusable KPI Metric Card
Your goal is to create a Shiny app that displays three different KPI cards (e.g., "Total Revenue", "Active Users", and "Conversion Rate"). Instead of writing the UI and server logic three times, you will build a single kpiCard module.
Requirements:
- Create a
kpiCardUIfunction that takes anidand alabel. It should contain:- A heading showing the
label. - A
numericInput(wrapped inns()) to allow the user to manually update the value. - A
textOutput(wrapped inns()) that displays the value with a prefix like "Current Value: ".
- A heading showing the
- Create a
kpiCardServerfunction that handles the logic to render the text output based on the numeric input. - In the main
ui, instantiate thekpiCardUIthree times with different labels: "Revenue", "Users", and "Conversion". - In the main
server, call thekpiCardServerthree times using matching IDs.
Success Criteria: You should be able to change the number in the "Revenue" card without the "Users" or "Conversion" cards updating. This proves your namespaces are isolated correctly.
There are no comments for now.