Dynamic UI in Shiny (incl. demo app) | by Thomas Filaire | Towards Data Science

Shiny has become an extremely popular solution to build interactive web apps straight from R. So popular that most data & analytics teams in companies have adopted this framework as part of their toolkit.
However, R Shiny is often said to be a good framework for fast prototyping, but not robust enough for industrialized, scalable solutions running into production.
With this series, I share some advanced tips with the ambition to overcome these objections and to help you build robust/ sustainable/ scalable applications, ready for business usage in production!
Production being defined as follows:
Software environments that are used and relied on by real users, with real consequences if things go wrong — Joe Cheng, CTO@RStudio
Note: in order to get the most from this tutorial, interested reader should already be familiar with basic Shiny principles (e.g. reactivity), R programing (e.g. function definition, tidyverse, purrr::map()), web development (e.g. html tags and css), and RStudio projects.
In this tutorial, I will focus on describing a reproducible way to dynamically generate UI components based on user inputs.
This feature is very useful in countless situations, as users of your applications are likely to display an a-priori unknown number of information on their dashboard. To list just a few situations:
Add/ remove stocks from a user portfolio management application
Consider/ ignore specific events in a time series forecasting application
Include/ exclude stores in a retailer performance analysis application
...
In addition, I’ll also describe how to define observers to trigger actions from dynamically generated buttons which is not a trivial task (as it requires a bit of JavaScript).
This tutorial comes with a R Shiny demo application which you can access here.
In this particular educational example, you (the user) can select as many Stranger Things characters as desired, and display basic information for each of them, through what I’ll call an infocard.
Each character’s infocard offers additional capabilities:
Access to the detailed Wikipedia page of the actor (clicking on the ‘More’ button)
Possibility to remove the infocard (clicking on the ‘Remove’ button)
My advice: in order to get the most out of this tutorial, I recommend to try rebuilding the application by combining the various pieces of code provided throughout the article. Main blocks are numbered in a logical way so that you do not get lost along the way. If necessary, the entire code is also available on my bitbucket account here. Please have a look to see where to store images in the project structure.
My approach to guide you through the code is the following:
Set the scene: description of required R packages, underlying dataset, and application backboneDefine user inputs: description of HTML widgets used in the application to capture user inputsDefine infocard function: a function to generate HTML code which will be very useful to generate infocards ‘on the fly’, in combination with the purrr::map() functionServer side definition: creation of reactive values and observers to make the application ‘shine’
Set the scene: description of required R packages, underlying dataset, and application backbone
Define user inputs: description of HTML widgets used in the application to capture user inputs
Define infocard function: a function to generate HTML code which will be very useful to generate infocards ‘on the fly’, in combination with the purrr::map() function
Server side definition: creation of reactive values and observers to make the application ‘shine’
To build the R Shiny companion app for this tutorial:
I used the 4 following packages: shiny, shinythemes, shinyWidgets, and tidyverse
# PART 1 - SETTING THE SCENE# section 1.0 - load required packageslibrary(shiny)          # web app framework for Rlibrary(shinythemes)    # themes for shinylibrary(shinyWidgets)   # custom input widgets for shinylibrary(tidyverse)      # collection of R packages for data science
I built a small dataset with basic information relative to main Stranger Things’ characters (source: Wikipedia):
# section 1.1 - build datasetcrm_tbl <- tibble(    character = c("Joyce Byers", "Jim Hopper",                   "Mike Wheeler", "Jane Hopper",                  "Dustin Henderson", "Lucas Sinclair",                  "Nancy Wheeler", "Jonathan Byers",                  "Karen Wheeler", "Will Byers",                  "Max Mayfield", "Steve Harrington"),    actor = c("Winona Ryder", "David Harbour",               "Finn Wolfhard", "Millie Bobby Brown",              "Gaten Matarazzo", "Caleb McLaughlin",              "Natalia Dyer", "Charlie Heaton",              "Cara Buono", "Noah Schnapp",              "Sadie Sink", "Joe Keery"),    image = c("img/winona_ryder.jpg", "img/david_harbour.jpg",               "img/finn_wolfhard.jpg", "img/millie_bobby_brown.jpg",              "img/gaten_matarazzo.jpg", "img/caleb_mclaughlin.jpg",              "img/natalia_dyer.jpg", "img/charlie_heaton.jpg",              "img/cara_buono.jpg", "img/noah_schnapp.jpg",              "img/sadie_sink.jpg", "img/joe_keery.jpg"),    wiki = c("https://en.wikipedia.org/wiki/Winona_Ryder",             "https://en.wikipedia.org/wiki/David_Harbour",             "https://en.wikipedia.org/wiki/Finn_Wolfhard",             "https://en.wikipedia.org/wiki/Millie_Bobby_Brown",             "https://en.wikipedia.org/wiki/Gaten_Matarazzo",             "https://en.wikipedia.org/wiki/Caleb_McLaughlin",             "https://en.wikipedia.org/wiki/Natalia_Dyer",             "https://en.wikipedia.org/wiki/Charlie_Heaton",             "https://en.wikipedia.org/wiki/Cara_Buono",             "https://en.wikipedia.org/wiki/Noah_Schnapp",             "https://en.wikipedia.org/wiki/Sadie_Sink",             "https://en.wikipedia.org/wiki/Joe_Keery"))
I chose to initialise the application with 2 active characters, meaning that 2 infocards will be displayed when the app starts:
# section 1.2 - set initial selection current_user_selection <- c("Jim Hopper", "Joyce Byers")
Lastly I built a basic backbone for the application, made of:
a navbarPage (blank bootstrap 3 page with top level navigation bar)
a left sidebar (I used shiny::wellPanel() function)
an infocard area
# PART 2 - UI PART# app backboneui <- navbarPage(    title = "R Shiny advanced tips series",    collapsible = TRUE,    windowTitle = "R Shiny tips",            tabPanel(        title = "Demo",                # section 2.1 - header                 # section 2.2 - sidebar panel        div(            class = "container",            column(                width = 3,                wellPanel(                    p("PLACEHOLDER - USER INPUTS")                )            ),            column(                width = 9,                div(                    # render thumbnails dynamically (ui)                    p("PLACEHOLDER - INFOCARDS")                )            )        )    ))# PART 3 - SERVER PARTserver <- function(input, output, session) {    }# PART 4 - RUN APPLICATIONshinyApp(ui = ui, server = server)
The header (backbone’s section 2.1) is a simple container where I display basic information about the application. I took advantage of existing bootstrap classes to make the text eye-catching (see page-header and lead classes)
# >> insert in section 2.1div(    class = "container",     h1(class = "page-header",        "Generate dynamic UI components",        tags$small("- Thomas Filaire")),                 p(class = "lead",        "Learn how to generate UI components dynamically based on user inputs"),                 p("Note: this application is the companion of my Medium post; read it to understand its underlying principles!"))
2 — DEFINE USER INPUTS
To illustrate the dynamic UI functionality, I built a very simple selector for the user to choose her/his favorite Stranger Things character. Once selected, just click on the ‘Add’ button to show the corresponding infocard.
While the basic selectInput() from shiny package is great and I use it most of the time, I made the choice to opt for the shinyWidget::pickerInput() alternative, which presents many benefits, in particular live search capabilities. Test it and I’m sure you’ll adopt it!
# >> insert inside wellPaneldiv(    shinyWidgets::pickerInput(         inputId  = "pi_character_selector",          label    = "Select character(s)",         choices  = crm_tbl %>% pull(character),         multiple = FALSE,         selected = "Mike Wheeler",         options = pickerOptions(                         actionsBox = FALSE,                         liveSearch = TRUE,                         size       = 10                         )     )),div(    actionButton(          class = "btn-primary",          inputId = "ab_show_character",           label = "Add"          )    ))
I built a function to generate infocard (thus an HTML piece of code), which I will call later on from the server side in a purrr::map() function to apply it to a list of Stranger Things characters.
As an R Shiny programers you should embrace functional programing as it forces you to create functions that can easily be analyzed in isolation, and avoid unnecessary duplication of code, improving readability.
The function takes a character’s name as an input, and return the infocard (HTML object). 3 key important things to notice:
note how rendering HTML piece of code is as easy as generating numbers, list, chart, etc., which we usually do with functions.note the usage of bootstrap 3's pre-existing classes (img-rounded, caption, btn-default), plus the rm_btn class assigned to the ‘remove button’ on which I will further elaborate in part 4 of this tutorial.note the usage of the immensely helpful stringr::str_glue() function which I used to assign unique ids to each button. This is a key part as ids must be unique and are required to trigger actions when respective buttons are clicked.
note how rendering HTML piece of code is as easy as generating numbers, list, chart, etc., which we usually do with functions.
note the usage of bootstrap 3's pre-existing classes (img-rounded, caption, btn-default), plus the rm_btn class assigned to the ‘remove button’ on which I will further elaborate in part 4 of this tutorial.
note the usage of the immensely helpful stringr::str_glue() function which I used to assign unique ids to each button. This is a key part as ids must be unique and are required to trigger actions when respective buttons are clicked.
str_glue() makes is easy to insert the value of a variable within a character string. In order to make each button’s id unique I adopted the following pattern:
for ‘more’ buttons: ab_more_character_name (ab stands for actionbutton).
for ‘remove’ buttons: ab_remove_character_name.
E.g. ab_more_joyce_byers, ab_remove_joyce_byers, etc.
I also used functions from the stringr package to lowercase/uppercase selected character’s name and to add/remove “_” so as to make string a valid id.
# >> insert infocard definition as section 1.3 in PART 1 - SETTING THE SCENE of your app.R file# section 1.3 - define custom infocardmy_infocard <- function(selected_character) {        # selected_character == value from user pickerInput    # e.g. "Mike Wheeler"        selected_image <- crm_tbl %>%         filter(character == selected_character) %>%         pull(image)  # get image url from dataset        selected_actor <- crm_tbl %>%         filter(character == selected_character) %>%         pull(actor)  # get actor's name from dataset        selected_wiki <- crm_tbl %>%         filter(character == selected_character) %>%         pull(wiki)   # get wikipedia's link from dataset        # piece of UI to render dynamically    column(        width = 4,        div(            class = "thumbnail text-center",                        # picture            img(                class = "img-rounded",                style = "height: 300px;",                src = selected_image            ),                        # main information            div(                class = "caption",                h4(selected_character),                p(selected_actor)            ),                        # link to wikipedia's page            actionButton(                class = "btn-default",                inputId = str_glue("ab_more_{selected_character %>%                          tolower() %>%                           str_replace_all(' ', '_')}"),                label = "More",                onclick = str_glue("window.open('{selected_wiki}', '_blank')")            ),            # remove button            actionButton(                class = "btn-default rm_btn",                inputId = str_glue("ab_remove_{selected_character %>%                           tolower() %>%                            str_replace_all(' ', '_')}"),                label = "Remove"            )        )    )}
Once we have defined the backbone of the application, and built the function to generate the custom-made infocards, let’s jump on the server side.
First, I used an eventReactive() function to capture any new character which the user selects through the pickerInput widget. eventReactive() allows the reactive components to update only when the user confirms her/his choice by clicking on the ‘Add’ button (which id is input$ab_show_character).
# >> insert in server part # get user's selected character when 'Add' button is clickedcurrent_character <- eventReactive(        eventExpr = input$ab_show_character, {            input$pi_character_selector        })
Whenever a new character is selected by the user, information must be captured and added to previous list of selected characters. I used a reactiveValues() to store selected characters, initialized with current_user_selection defined as per part 1.
An observeEvent updates reactive_values whenever the ‘Add’ button is clicked. I inserted a unique() in the pipeline to make sure infocards are note created multiple times, and thus are displayed only once.
# >> insert in server part# store user's selectionreactive_values <- reactiveValues()reactive_values$character_list <- current_user_selection# add character when relevantobserveEvent(input$ab_show_character, {    reactive_values$character_list <-         c(reactive_values$character_list,           current_character()) %>%         unique()})
Based on reactive_values$character_list (the list of user’s chosen characters), I generate as many infocards as necessary inside a renderUI block, which is then ‘rendered’ in the UI via a corresponding uiOutput:
# >> insert in the main area of ui sectionuiOutput(outputId = "infocards")
To build the UI part, I passed the list of characters within the purrr::map() function so as to call the custom my_infocard() function as many times as required. As purrr::map() returns a list, I pipe the result with the tagList() function to bind outputs together and generate a unique (and potentially big) HTML section.
See in the screenshot below a visual explanation of how tagList() works:
At this stage of development, the application should be working almost as desired.
The final touch
The last functionality to add is the ability to remove an infocard by clicking on the ‘Remove’ button.
I used a bit of JavaScript here. I will not cover JavaScript in details (I’m far from being a expert in this domain), but simply notice here that:
JavaScript is inserted in the HTML code of the R Shiny application via tags (head and script)
The code is simply storing button id (rm_btn_id) whenever a button with the rm_btn class is clicked
# >> insert in the tabPanel, below title# JS function to identify button id when clickedtags$head(    tags$script(        HTML("$(document).on('click', '.rm_btn', function () {                        Shiny.onInputChange('rm_btn_id', this.id);                    });"        )    )),
As ‘Remove’ buttons have the rm_btn class (see definition in section 1.3), Shiny will get access to their respective id whenever they are clicked. Finally, I take advantage of this in order to remove the corresponding character from the characters’ list, through an observeEvent().
# >> insert in server part# remove infocardobserveEvent(input$rm_btn_id, {    reactive_values$character_list <-        reactive_values$character_list %>%        .[reactive_values$character_list %>%             toupper() != input$rm_btn_id %>%             str_remove("ab_remove_") %>%             toupper() %>%             str_replace("_", " ")]})
And we’re done! Congratulations if you’ve been able to made it so far!.
shinythemes is a great package to super-easily make your application look professional
the sequence : list of user inputs + map( “function that generates HTML code”) + tagList() is extremely powerful and — in my opinion — readable.
create function to generate pieces of HTML to avoid duplication of code and easily use the above sequence
I hope you found this article useful and that it will help you build better applications. You can find the entire code on my bitbucket repo here.
Please do not hesitate to send any feedback or questions!
SOURCES
R Shiny cheatsheet
Communicating with Shiny via JavaScript
Business Science 202A — Build scalable applications using R