Shiny app filling output that depends on input does not work

1.5k views Asked by At

I'm working on a some text mining. Based on the user's input I'm generating a number of suggestions for the next word. This part works fine. However the number of suggestions can be very large, so I want to show at most 10 suggestions in Shiny and I don't want to show NA values.

I created a reproducable example to exhibits the same problem. The trick I'm trying to use is pasting "suggestions" with i. This works when my output does not depend on my input. I got this from http://shiny.rstudio.com/gallery/creating-a-ui-from-a-loop.html.

My ui.R file

    library(shiny)

    fluidPage(
            titlePanel("Test"),
            fluidRow(
                    textAreaInput("userText", label="Enter your text")
            ),
            fluidRow(
                    lapply(1:5, function(i) {
                            textOutput(paste0("suggestions", i))})
            )
    )

My server.R

    library(shiny)

    mySuggestions <- c("this", "is", "a", "test", "of", "getting", "reactive", "list", "length")

    function(input, output, session) {

            getWords <- function(i, wrds) {
                    output[[paste0("suggestions", i)]] <- renderText({ wrds()[i] })
            }

            userText <- reactive({ 
                    # Leaves this function when input$userText is NULL or ""
                    req(input$userText)
                    input$userText })

            words <- reactive({
                    mySuggestions[1:userText()]
            })
            # Problem
            lapply(reactive({ 1:min(5,  length(words())) }), getWords(), wrds=words()) 
    }

When you enter a positive integer in the ui text field the app is supposed to show as many words, but 5 at most.

The above version of the server.R results in a warning "Warning: Error in paste0: argument "i" is missing, with no default" I've tried several versions for this problematic line.

    reactive({ lapply(1:min(5,  length(words())), getWords(), wrds=words() ) })

Gives no errors, but it shows nothing in the output.

    lapply(1:min(5,  length(words())), getWords() , wrds=words())

Results in a warning "Warning: Error in paste0: argument "i" is missing, with no default"

    lapply(reactive({1:min(5,  length(words()))}), getWords(), wrds=words())

Results in a warning "Warning: Error in paste0: argument "i" is missing, with no default"

     lapply(reactive({1:min(5,  length(words))}), function(i) {
              output[[paste0("suggestions", i)]] <- renderText({ words[i] }) } )

Results in Error in as.vector(x, "list") : cannot coerce type 'closure' to vector of type 'list'

     lapply(reactive({1:min(5,  length(words()))}), function(i) {
              output[[paste0("suggestions", i)]] <- renderText({ words()[i] }) } )

Results in Error in as.vector(x, "list") : cannot coerce type 'closure' to vector of type 'list'

     reactive({lapply(1:min(5,  length(words)), function(i) {
              output[[paste0("suggestions", i)]] <- renderText({ words[i] }) }) })

Gives no errors, but it shows nothing in the output.

     reactive({lapply(1:min(5,  length(words())), function(i) {
              output[[paste0("suggestions", i)]] <- renderText({ words()[i] }) }) })

Gives no errors, but it shows nothing in the output.

     lapply(1:min(5,  reactive({ length(words )})), function(i) {
             output[[paste0("suggestions", i)]] <- renderText({ words[i] }) }) 

Results in Error in min(5, reactive({ : invalid 'type' (closure) of argument

     lapply(1:min(5,  reactive({ length(words() )})), function(i) {
             output[[paste0("suggestions", i)]] <- renderText({ words()[i] }) }) 

Results in Error in min(5, reactive({ : invalid 'type' (closure) of argument

Now the following line shows the entered number of words in a single text field. When I enter 2 it shows 2 words and when I enter 20 it shows 5 words. This is the behaviour I want, but I want each word in a separate text field.

    output$suggestions1 <- renderText(words()[1:min(5, length(words()))])

I'm getting lost. I was getting so desperate that I tried a few things I did not expect to work. Is it possible to do what I want? If so, how? If not, what is the problem? I haven't found anything yet that addresses this specific problem.

1

There are 1 answers

3
Etienne Moerman On

The combination of outputUI and renderUI works great and keeps the code relatively simple.

ui.R

    ...
    fluidRow(
            uiOutput("suggestions")
    )

server.R

    library(shiny)

    mySuggestions <- c("this", "is", "a", "test", "of", "getting", "reactive", "list", "length")

    function(input, output, session) {

            ...

            output$suggestions <- renderUI({ 
                    lapply(1:min(5, length(words())), function(i) {
                    output[[paste0("suggestions", i)]] <- renderText({ words()[i] })
            }) })
    }

I didn't know what outputUI and renderUI did, but they seem perfect for situations like these.