“The reason for Shiny’s slow action [is] usually not Shiny.” – Winston Chang
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. Slow performance is a primary reason that users lose interest in an application. 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. 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.
Anyone who has built their first Old Faithful Geyser R Shiny app is familiar with how a Shiny app is split into two parts – ui and server. 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. While tempting, relying too much on the renderUI function will slow down performance. 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:
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 updateInput and renderUI. Therefore, one may think there is no risk to using renderUI 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 input, 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? The flickering of the input created by reactivity occurs because the rendering is done on the server rather than in the browser. 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.
Here’s an open secret: under the hood, an R Shiny app is in fact R code transformed into HTML.
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 shinyjs 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 tutorial by R Studio on how to implement CSS within a Shiny app, but I would encourage you to go a step further and use SASS. Whilst SASS is beyond the scope of this article, I can recommend a nice overview of SASS by my colleague Pedro Silva. Pedro also has a great primer on getting started with CSS and R Shiny.
Both buttons start with the arrow-up icon, but they are modified in a very different way.
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!