Prevent constant refresh of a uiOutput entity

I have a rather complex uiOutput that is basically a table of a dynamic number of elements, organised by rows.
Each row contains various UI controls. Each UI control affects the state of the table, as well as the presence and values of other controls in the same row.

I currently have something that works, by using uiOutput and a bunch of dynamically created ui entities and observe bindings, however the problem is that every time I interact with the controls, the whole table is refreshed, producing a very slow interactive operation and an annoying "flash" of the whole table.

How can I improve this?

If you are using DT for the table, then you could try dataTableProxy()

No I am not, and I don't want to. It looks awful.

You could potentially style ugly things with css.
What are you using, are you sticking with pure shiny for this?

A minimal reprex might help crystallise the discussion. My instinct is that there probably isn't a good pure shiny solution for you.

Sorry but unfortunately I don't have time to style stuff, nor to write a rather complex example of this, because I would have to extract it from my current code which would require a considerable effort.

Briefly described, imagine that i have a table that is rendered as uiOutput(). The table does not know how many elements it has, so it must be generated dynamically. Note that I am not referring to a datatable. I am referring to a hand crafted table with actual html markup. It could be a UL list, but for now I found a table a viable option. In practice, I suspect at the end it will be a series of divs, rather than a table.

Now, in this table, for each row, I have the value, and two comboboxes. However, one combo (the second) has a value and the allowed set of values depending on the selection of the first. Now you have a dependency of the contents of the combo. When you change the first combo, the second combo is updated, but this changes the underlying data in the model, which triggers a refresh of the table as a whole, because there's no way to isolate individual rows. Shiny can only refresh the ui element that is the whole table, which is dynamic on the number of rows.

The point is that if you have uiOutput, it can't act both as an input and as an output (or an output containing input elements that change underlying data that are then outputted). If you change the content of the table, it changes the underlying data, which triggers a refresh of the whole table.

That's what I am trying to prevent.

No need to apologise, I'm just making the best suggestions I can, but I wouldn't expect them to fit every case.

I think I understand, and that in principle shiny ... doesnt support this.

What you describe is adjusting rendered items in the browser (in javascript presumably), rather than rerendering them in the traditional shiny way.

To date I would only expect to find such complex manipulations within highly developed specialist packages, that offer htmlwidgets that anticipate this need, by providing the requisite javascript to achieve these aims. Succesful example of this type include DT, Leaflet and Plotly... these packages offer 'proxy' functions.

The proxy functions of these packages allow rendered objects to be manipulated on the fly adjusted without 'rerendering' in the pure shiny fashion i.e. invalidating and going through the normal render steps.

I doubt that there is at present an out of box solution for you, but I will keep an eye out, and perhaps others might chime in. I hope that perhaps in the future rstudio/gt package might advance far enough to be a good rival to DT (which does have its problems of course)

That's what I feared. Thank you for that.

For now, I am working around the issue by:

  • splitting it into two tables, so that combobox1 is never present at the same time with combobox2. This prevents the triggering.
  • As a separate requirement, I have to add some statement of error if the selected value in combobox1 is not valid for the data I am handling. This was also a problem and would require a refresh of the table. I worked around that by creating a javascript function that is called via sendCustomMessage. The javascript side uses jquery to just add or remove the appropriate div from the table position.

As a general rule from now on, I follow this, if any entity needs to act both as an input and as an output:

  • render it once with renderUI,
  • bind (possibly dynamically, since in general you don't know what elements you have when you actually render the UI) all the generated dynamic inputs. The associated bind controller functions will update the backend model object.
  • make the renderUI react on whatever backend object contains the fundamental state to render (e.g. in this case, react against what determines the size of the table, not its content). Do not have it bind against input elements you generate inside the renderUI, nor their representation on the backend object (otherwise, as said, when you change it in the ui inputs, it will trigger a refresh of the whole thing)
  • funnel any other state modification to the table via javascript.

It's a tentative, but it might work. I'll have to explore it. In any case, the alarm bell is clear: when a dynamically rendered entity is both an input and an output, you are in trouble.

1 Like