Make R Shiny Dashboards Faster with updateInput, CSS, and JavaScript

By:
Marcin Dubel
August 16, 2020

<h3>TL;DR</h3> <b>Nobody wants to use a slow dashboard.</b> At Appsilon, we frequently hear this complaint from clients: “We’ve built a fantastic Shiny dashboard, but it’s incredibly slow and no one wants to use it.” No matter how complicated they are, Shiny dashboards do not have to be slow! One powerful way to speed up the performance of R Shiny applications is to leverage Shiny’s web nature and push actions to the browser. This article will show you how to omit the server bottleneck with updateInput, leverage CSS classes, and take advantage of JavaScript actions for smoother Shiny performance. <blockquote><i>“The reason for Shiny’s slow action [is] usually not Shiny.” – Winston Chang</i></blockquote> <ul><li><a href="#server">Introduction: server.R Slows Down Shiny</a></li><li><a href="#updateInput">Speed Up R Shiny Performance with updateInput</a></li><li><a href="#css">Enhance R Shiny UI with CSS Classes</a></li><li><a href="#javascript">Using R Shiny with JavaScript Actions</a></li><li><a href="#more">Learn More: Shiny Tutorials</a></li></ul> <h3 id="server">Introduction: server.R Slows Down Shiny</h3> It is easy to fall in love with R Shiny’s capacity for reactivity. The ability to parametrize every item in the server.R file can be particularly tempting for those with R programming rather than a web app development background. As always, with great power comes great responsibility, and overusing this feature may lead to significant application performance issues. This can lead to a long initial loading time, where elements slowly pop onto the screen one by one. Users might also be forced to wait a few seconds after each click. <b>Slow performance is a primary reason that users lose interest in an application.</b> No matter how great your application is, it needs to be fast to be a success! This huge negative impact on performance might not become apparent until the product reaches maturity and is hard to refactor because the codebase is large and complex. <b>Adhering to the rules and tips discussed in this article from the very start of your project is the key to developing efficient R Shiny apps whilst taking advantage of the reactivity feature.</b> <img class="aligncenter wp-image-5127" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b021ff541bf136cb7cbb77_old-faithful.webp" alt="Old Faithful Geyser" width="640" height="428" /> <h3 id="updateInput">Speed Up R Shiny Performance with updateInput</h3> Anyone who has built their first Old Faithful Geyser R Shiny app is familiar with how a Shiny app is split into two parts – <i>ui</i> and <i>server</i>. Typically, application development begins with having these two worlds separated, i.e. widgets (UI elements) and logic are kept separate in the ui.R and server.R files respectively. However, the developer eventually gets to a point where the UI itself is dependent on the behavior of other widgets, user input, actions, and clicks. There is a temptation to pack everything into the renderUI function and let reactivity do the work. <b>While tempting, relying too much on the renderUI function will slow down performance. </b>The developer can speed up the R Shiny application significantly with just a bit more code, as I present in the following example.  Consider these two strategies to deploy two equivalent numeric inputs updated by a button: <figure class="highlight"> <pre><code class="language-r" data-lang="r">library(shiny) <br>ui <- fluidPage(  titlePanel("Shiny Super Solutions 6!"),    numericInput(    "update_input",    label = "I will be updated using updateInput",    value = 0,    min = 0,    max = 10   ),   uiOutput("render_input"),    actionButton(   "click_button",   label = "I will update numeric inputs!"  ) )   server <- function(input, output, session) {    output$render_input <- renderUI({    numericInput(      "render_input",      label = "I will be updated using reactivity",      value = if(is.null(input$click_button)) {         0      } else {         input$click_button      },      min = 0,      max = 10    )   })  observeEvent(input$click_button, {    updateNumericInput(      session,      "update_input",      value = input$click_button    )  }) } <br>shinyApp(ui, server) </code></pre> </figure> At first glance their resulting behavior is identical: As long as the application is small, it is difficult to spot any difference in performance between <i>updateInput</i> and <i>renderUI</i>. Therefore, one may think there is no risk to using <i>renderUI</i> in the initial stages of app development…at least until the app grows in size, when performance issues become apparent. Think about the problem this way: when Shiny spots a difference in the <i>input</i>, it goes through all the elements that react to it. The render solution recreates the entire widget, including the label, border and other features. In most cases we are only interested in modifying the value inside, so we should focus on this task only. Also note what happens when the app starts (I’m refreshing the page in this graphic): Do you see how the reactivity section pops in and out of existence?<b> The flickering of the input created by reactivity occurs because the rendering is done on the server rather than in the browser. </b>First a Shiny app first goes through the UI portion, creates all the features coded there, and then reaches out to the server components. In a real app, handling UI via the server will result in poor UX: after opening the app, the user will see mostly blank spaces that slowly fill in with content one by one, causing a lot of distraction and potentially even confusion. Instead, all widgets should already be there, waiting for the user, and only small portions (such as a value inside of a field) should be left open to modification on the fly. <h3 id="css">Enhance R Shiny UI with CSS Classes</h3> It is possible (and even simple) to make use of JavaScript and CSS rules to style the content within an R Shiny app. Once the app is running in the browser, it should be looked at as any other web page from the UI perspective even if there is R code involved. <b>Under the hood, an R Shiny app is in fact R code transformed into HTML.</b> Therefore, R Shiny apps can be modified with CSS just like web apps. Using CSS allows you to enhance the style of your app beyond, for instance, the basic “primary-blue, warning-orange, danger-red” options in stock Shiny applications. <blockquote>Here’s an open secret: under the hood, an R Shiny app is in fact R code transformed into HTML.</blockquote> The most efficient way to assign styles are CSS classes. You can use them to e.g., wrap your content in `div(class = ‘myclass’, ...)`. Using IDs for the various elements can help with constructing selectors – instructions for the browser that indicate which elements you are interested in modifying.  The ability to simply switch classes further streamlines the style modification process. If you are an R user I would recommend that you check out the excellent <a href="https://deanattali.com/shinyjs/" target="_blank" rel="noopener noreferrer"><i>shinyjs</i></a> package by Dean Attali. It will help you with web modifications starting at the code level, so the transition into frontend coding will be smooth. Remember that you need to define classes before you start triggering them on and off. There is a comprehensive <a href="https://shiny.rstudio.com/articles/css.html" target="_blank" rel="noopener noreferrer">tutorial by R Studio</a> on how to implement CSS within a Shiny app, but I would encourage you to go a step further and use <a href="https://rstudio.github.io/sass/" target="_blank" rel="noopener noreferrer">SASS</a>. Whilst SASS is beyond the scope of this article, I can recommend a <a href="https://appsilon.com/how-to-make-your-css-awesome-with-sass/" target="_blank" rel="noopener noreferrer">nice overview of SASS</a> by my colleague Pedro Silva. Pedro also has a great primer on <a href="https://appsilon.com/howto-css-and-shiny/" target="_blank" rel="noopener noreferrer">getting started with CSS and R Shiny</a>.  <h3 id="javascript">Using JavaScript Actions with R Shiny</h3> Because Shiny applications can be treated like any other webpage once they are in the browser, we can also make use of JavaScript code to enhance them. Using JavaScript allows for keeping an action’s logic inside the browser rather than sending the action trigger to the server and slowing down the app. This is beneficial, because there is a resource overhead for passing information between the ui and the server. Therefore, it is best to avoid communicating with the server if there is no need to go beyond the UI code. Consider this equivalent buttons example: <figure class="highlight"> <pre><code class="language-r" data-lang="r">library(shiny) library(shinyjs) ui <- fluidPage(  useShinyjs(),  titlePanel("Shiny Super Solutions 6!"),    actionButton(    "js_update",    label = "I will be updated using javascript",    icon = icon("arrow-up")  ),     uiOutput("shiny_update"),  actionButton(    "click_button",    label = "I will update icons!",    onclick = "    $('#js_update > i').toggleClass('fa-arrow-up');    $('#js_update > i').toggleClass('fa-arrow-down');    "  ) )   server <- function(input, output, session) {    output$shiny_update <- renderUI({    clicks <- input$click_button    icon_status_up <-      is.null(clicks) || clicks %% 2 == 0    icon_name <- if(icon_status_up) {      "arrow-up"    } else {      "arrow-down"    } <br>    actionButton(      "shiny_update",      label = "I will be updated using reactivity",      icon = icon(icon_name)    )  }) }   shinyApp(ui, server) </code></pre> </figure> Both buttons start with the <i>arrow-up</i> icon, but they are modified in a very different way. After each click in the “classic” Shiny solution (<i>shiny_update</i> button), information is passed to the server, processed with R code and the whole button is re-rendered. In the JavaScript-based solution (<i>js_update </i>button) the information never exits the browser or communicates with the server, making it a very fast solution (note also that less code was used and that the entire logic is in a single file). In this example, JavaScript instructions are added inline to the R code. For more complicated solutions I recommend adding additional, independent JS scripts. You can learn more about connecting Shiny with JavaScript <a href="https://shiny.rstudio.com/articles/communicating-with-js.html" target="_blank" rel="noopener noreferrer">here</a>. In our example, the solutions are truly equivalent and while it’s technically still possible, it is very difficult to spot the difference. However, small changes like this will be crucial as your Shiny app grows in complexity! <img class="aligncenter size-full wp-image-5122" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b022039f2634632a51623b_3.-arrows.gif" alt="Shiny Arrows" width="350" height="310" /> These three simple tools – updateInput, CSS, and Javascript – will bring your Shiny projects to an entirely new level. Need help with an enterprise Shiny dashboard? Reach out to us at hello@wordpress.appsilon.com! <h3 id="more">Learn More</h3><ul><li>Appsilon engineer Krystian Igras's guest post on RStudio's blog: <a href="https://blog.rstudio.com/2020/07/21/4-tips-to-make-your-shiny-dashboard-faster/" target="_blank" rel="noopener noreferrer">4 Tips to Make Your R Shiny Dashboard Faster</a></li><li>Appsilon engineer Pedro Silva's <a href="https://appsilon.com/how-to-make-your-css-awesome-with-sass/" target="_blank" rel="noopener noreferrer">Guide to SASS</a></li><li>Pedro Silva's <a href="https://appsilon.com/howto-css-and-shiny/" target="_blank" rel="noopener noreferrer">Guide to using CSS with Shiny</a></li></ul>

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
speed up shiny
shiny dashboards
r
rstudio
tutorials