5

I'm trying to create an app that just adds text to the mainPanel. However, the text output is very slow when adding text.

I'd like to make this instant and fast instead of it taking so much time. Is there a way to make it be processed in the browser instead of going to R?

Code

library(shiny)

ui <- fluidPage(sidebarLayout(
  sidebarPanel(textInput("text", label = NULL)),
  mainPanel(textOutput("textout"))
))


server <- function(input, output, session) {
  
  output$textout <- renderText({
    input$text
  })
}

shinyApp(ui, server)

enter image description here

writer_typer
  • 708
  • 7
  • 25
  • I don't have a solution, but I think it's the `textInput` that is being throttled rather than the output. Can't find any way to change that behaviour. – pseudospin Feb 06 '21 at 19:10

2 Answers2

4

This is due to the way the inputs are used by shiny. In the javascript it has an option with a 'debounce' of 250ms which explains why it only updates after you stop typing for a quarter of a second.

You can override this but it seems to involve writing a replacement for textInput. The crucial bit is the getRatePolicy function in the javascript.

enter image description here

library(shiny)
library(shinyCustom)

textinput_script <- "
<script>
var customTextInputBinding = $.extend({}, Shiny.inputBindings.bindingNames['shiny.textInput'].binding, {
  find: function(scope) {
    return $(scope).find('input.customTextInput');
  },
  subscribe: function(el, callback) {
    $(el).on('keyup.customTextInputBinding input.customTextInputBinding', function(event) {
      callback();
    });
    $(el).on('focusout.customTextInputBinding', function(event) { // on losing focus
      callback();
    });
  },
  unsubscribe: function(el) {
    $(el).off('.customTextInputBinding');
  },
  getRatePolicy: function() {
    return {
      policy: 'direct'
    };
  }
});

Shiny.inputBindings.register(customTextInputBinding, 'shiny.customTextInput');
</script>
"

ui <- fluidPage(sidebarLayout(
  sidebarPanel(
    HTML(textinput_script),
    customTextInput("text", label = NULL)
  ),
  mainPanel(textOutput("textout"))
))

server <- function(input, output, session) {
  output$textout <- renderText({
    input$text
  })
}

shinyApp(ui, server)

This is cannibalized from here, and the actual guide for doing this in general is here.

pseudospin
  • 2,737
  • 1
  • 4
  • 19
  • I'm looking to do the opposite and increase the `getRatePolicy` but when trying your code I receive this error `Error in customTextInput("text", label = NULL) : could not find function "customTextInput"` – Quixotic22 Nov 20 '21 at 01:41
  • Looks like it works again if you include `library(shinyCustom)`. – pseudospin Nov 28 '21 at 22:15
1

This approach does not go after the cause of the delay (like @pseudospins great answer) but rather uses a crutch. It is a shiny-only solution which is why it may still be of some interest.

It uses the "greedier" reactivePoll. The check function always yields TRUE to make sure that input$text gets evaluated after every polling interval no matter what. The argument intervalMillis can be changed to fit the requirements of the app. Here it is set to 1/10 of a second.

Please note, that this approach is simple and effective but it can make your app very slow if you overdo it. The check argument may be a chance to save resources. You can try storing the length of the last string and compare it to the new state every after every intervalMillis interval. In this simple example, though, I doubt that will help much.

library(shiny)

ui <- fluidPage(sidebarLayout(
  sidebarPanel(textInput("text", label = NULL)),
  mainPanel(textOutput("textout"))
))


server <- function(input, output, session) {
  text <- reactivePoll(intervalMillis = 100, session, 
                       checkFunc = function() TRUE, 
                       valueFunc = function () input$text
                      )
  
  output$textout <- renderText({
    text()
  })
}

shinyApp(ui, server)

enter image description here

Jan
  • 4,974
  • 3
  • 26
  • 43