Chapter 9 Dynamic UI

As well as updating the content of the outputs, sometimes you will actually want to add more, or modify the inputs, or generally large parts of your app with code. Generally, you want to avoid these techniques except when they are necessary as they add substantially more moving parts to the process. But when you need them, you need them.

In Chapter XYZ, we’ll come back to more advanced tecnhiques that require you know a little more about HTML and javascript.

9.1 Modify existing inputs

Every input control, e.g. textInput(), is paired with a update function, e.g. updateTextInput(), that allows you to modify the control after it has been created in the UI. Take this very simple example. The app has three numeric inputs, where the first two control the range (the min and max) of the last.

Note that I’ve used observeEvent() here, although observe() would also have worked and would yield shorter code. I think it’s best to be very clear exactly what change you are listening for, and what action you want to take in response.

For historical reasons, calls to updateXXXXInput() look a little different to other Shiny functions, as you need to pass session as the first argument. (If you’re using an older server function template, you might not have session in the arguments, so make sure you add it if its missing.)

All updates are performed “simultaneously” (for the purposes of reactivity) once all outputs are done.

9.1.2 Hierarchical select boxes

A more complicated, but particularly useful, application of the update functions is to allow interactive drill down across multiple categories. I’m going to start some imaginary data for a sales dashboard, coming from https://www.kaggle.com/kyanyoga/sample-sales-data.

For our purposes, I’m going to focus on a natural hierarchy in the data:

  • Each territory contains customers
  • Each customer has multiple orders
  • Each order contains rows

I want to create a user interface where you can:

  • Select a territory to see all customers.
  • Select customer to see all orders.
  • Select order to see data.

The basic idea is to start with UI that contains three select boxes, and one output table. The choices for the customername and ordernumber will be dynamically supplied so, I explicitly set choices = NULL.

Then in the server function, I work top-down, first creating a filtered subset that only contains the selected territory and using that to update input$customername, then creating a subset contains the given customer and using that to update input$ordernumber.

You can see a more fleshed out application of this principle in https://github.com/hadley/mastering-shiny/tree/master/sales-dashboard.

9.1.3 Circular references

Ther’es an important drawback to using the update functions that you need to be aware of. From Shiny’s perspectve, when modifying value, the update functions act exactly as if the user has altered the value, so that the update functions can trigger reactive updates in exactly that same way a human can. This means that you’re now stepping outside of the bounds of pure reactive programming, and you need to start worrying about circular references and creating infinite loops.

For example, take the following simple app. It contains a single input control, and a observer that increments its value by one. Every time updateNumericInput() runs, it invalidates input$n, causing updateNumericInput() to run again, so the app is stuck in an infinite loop constantly increasing the value of input$n.

You’re unlikely to create such an obvious problem in your own app, but beware this potential problem if you are updating multiple controls that depend on each other. You will need to carefully reason through the updates to ensure that you’re not creating an infinite loop of updates. This is a very good reason that you should only update functions for the most important cases.

This is generally only a concern when you are changing the value, be aware that changing some other settings can implicit change the value, e.g. changing set of choices for inputSelect().

9.2 uiOutput() and renderUI()

There’s a special UI component that allows you to generate components of the UI on the server.

tagList() if you need mutliple controls. Want to keep as much as of the fixed structure in UI as possible. Better peformance. Simpler reasoning.

Output control. Replaces previously generated HTML.

Notice that the value you have selected is wiped out when you change the label. This is one of the reasons why, where possible, you should use an update function instead of renderUI(). Also note that it takes a fraction of a second to appear after the app loads - that’s because it has to be rendered by the server function.

Note that you are now creating IDs in two places so that when you add to ui, you have to be careful not to call the control dynamic. Later, in Chapter 10 we’ll see how modules can help avoid this problem by namespacing the control.