Lesson 23 / 25

Interactive Apps with Shiny

Build a small interactive Shiny app with inputs, outputs and reactivity.

Analysis that users can explore

Shiny turns R code into interactive web applications without writing JavaScript. An app has a UI (layout, inputs such as selectInput, dateRangeInput and sliderInput, and outputs such as plotOutput and tableOutput) and a server function that computes outputs with render functions (renderPlot, renderTable, renderText). Shiny's core idea is reactivity: outputs that read input$city automatically recompute when the user changes that input. reactive() expressions cache intermediate results shared by several outputs, avoiding repeated work, and observeEvent() runs side effects such as saving data when a button is clicked. bslib provides modern Bootstrap layouts and theming, and modules break larger apps into reusable parts. Deploy apps on Posit Connect, shinyapps.io, or your own Shiny Server or Docker container. Keep heavy data preparation out of the app, precomputing summaries where possible, so the app stays responsive. Shiny is also available for Python.

A small sales explorer app

Inputs drive a shared reactive expression and two outputs.

library(shiny)
library(bslib)
library(dplyr)
library(ggplot2)

sales <- readr::read_rds("data/sales_clean.rds")

ui <- page_sidebar(
  title = "Sales explorer",
  sidebar = sidebar(
    selectInput("city", "City", choices = sort(unique(sales$city))),
    dateRangeInput("dates", "Dates", start = min(sales$order_date), end = max(sales$order_date))
  ),
  card(plotOutput("daily")),
  card(tableOutput("top_orders"))
)

server <- function(input, output, session) {
  filtered <- reactive({
    sales |>
      filter(city == input$city,
             order_date >= input$dates[1], order_date <= input$dates[2])
  })                                              # recomputed only when inputs change

  output$daily <- renderPlot({
    filtered() |>
      summarise(revenue = sum(amount_inr), .by = order_date) |>
      ggplot(aes(order_date, revenue)) + geom_line()
  })

  output$top_orders <- renderTable({
    filtered() |> slice_max(amount_inr, n = 5) |> select(order_id, order_date, amount_inr)
  })
}

shinyApp(ui, server)

A spreadsheet that recalculates

Shiny reactivity works like spreadsheet formulas: change one input cell and every dependent cell updates automatically, while cells that do not depend on it stay as they are.

Quick check: What does a reactive() expression provide in Shiny?

  • A static value computed once at start-up
  • A cached result that recomputes only when the inputs it reads change, and can be shared by several outputs
  • A way to write JavaScript
  • A database connection
Answer

A cached result that recomputes only when the inputs it reads change, and can be shared by several outputs — reactive() expressions cache derived data and update when their dependencies change.