Search code examples
rshinystringr

Modify textInput() via stringr in Shiny


Question: Is there a way to permanently modify the input from textInput that I can use it in the Rmd file? Alternatively, is there a way to copy the text from textInput into a different reactive value, modify it with the function, and use the new reactive in the Rmd file? The various ways I have tried have not been successful.

Details: I am creating an exercise in Shiny for students where they make predictions, then review the results and describe whether the results match their predictions. Their predictions are made on one tab in Shiny via textInput()s. After they enter their predictions, that tab is hidden for the remainder of the exercise. I do this so that they cannot modify their predictions after viewing the results.

Their predictions, the graphs, and their interpretations are then converted to a PDF file via an R markdown (Rmd) file. The PDF is generated via pdfLaTex (lualatex engine).

Students will often use reserved LaTeX characters (e.g., #, $, and %) in their responses, such as "My prediction for #2 was correct." or "The decrease was about 20%." The special characters need to be escaped with "\" to be used as literal characters with the LaTex engine.

I wrote a small function using stringr to replace the special characters with escaped versions. The function works fine as shown in the MWE below.

I would like to replace any LaTeX reserved characters from textInput() with escaped versions. Everything I have tried returns an error such as Can't modify read-only reactive value 'text_in' or other types of errors. I understand that modifying user input could be confusing to the user but in my apps the user never sees their input from the predictions until the final report is generated. Because the LaTeX characters are properly escaped, their predictions in the report look look identical to their initial input.

Right now, I can call the function from the Rmd file,

`r fix_special_chars(input$text_in)`

which works fine but I have to ensure I copy and paste the function where needed in the .Rmd file(s). It seems cleaner to me if I can use the function within the Shiny code after their predictions are input and they move to the results.

MWE with example attempts in comments

library(shiny)
library(stringr)

# Function to replace 4 special LaTeX chars with
# escaped versions.
fix_special_chars <- function(str = NULL){
  str_replace_all(str, "([#%$_])", "\\\\\\1")
}

ui <- fluidPage(

  titlePanel("Escape LaTeX special chars"),

  fluidRow(
    column(
      4,
      textInput("text_in",
        label = "Enter special chars like # and %",
        width = "95%"
      )
    ),
    column(
      4,
      p(strong("Here's the output")),
      textOutput("text_out")
    ),
    
    # Goes with example attempt below.
    # column(
    #   4,
    #   p(strong("Here's the output")),
    #   textOutput("text_out_fixed")
    # )
    
  )
)

server <- function(input, output) {

  # Shows the function works in the UI output but
  # input$text_in is not modified.  
  output$text_out <- renderText({
    fix_special_chars(input$text_in)
  })
  
  
  ## Example attempts
  
  # No success defining the modified string as a reactiveVal()
  # even if using in place of text_in_mod below.
  
  # text_in_new <- reactiveVal()

  
  # -----
  # This puts the modified string in to text_in_mod but
  # `r text_in_mod` and `r output$text_out` do not work in the Rmd file.
  
  # output$text_out <- renderText({
  #   text_in_mod <- isolate(fix_special_chars(input$text_in))
  #   print(text_in_mod). # Prints successful escaping to console
  #   
  #   fix_special_chars(input$text_in)
  # })

  
  # -----
  # I've tried variations of this theme but 
  # I get an "Error in cat: argument 1 (type 'closure') error.
  
  # text_in_mod <- reactive({
  #   isolate(fix_special_chars(input$text_in))
  # }) 
  # 
  # output$text_out_fixed <- renderText({
  #   text_in_mod()
  # })
}

shinyApp(ui = ui, server = server)
Shiny applications not supported in static R Markdown documents

Created on 2023-09-19 with reprex v2.0.2


Solution

  • library(shiny)
    library(stringr)
    shiny::devmode()
    # Function to replace 4 special LaTeX chars with
    # escaped versions.
    fix_special_chars <- function(str = NULL) {
      str_replace_all(str, "([#%$_])", "\\\\\\1")
    }
    
    ui <- fluidPage(
      titlePanel("Escape LaTeX special chars"),
      fluidRow(
        column(
          4,
          textInput("text_in",
            label = "Enter special chars like # and %",
            width = "95%"
          )
        ),
        column(
          4,
          p(strong("Here's the raw output")),
          textOutput("text_out")
        ),
        column(
          4,
          p(strong("Here's the fixed output")),
          textOutput("text_out_fixed")
        )
      )
    )
    
    server <- function(input, output) {
      
      output$text_out <- renderText({
        req(input$text_in)
      })
    
      text_in_new <- reactive({
        fix_special_chars(req(input$text_in))
      })
    
      output$text_out_fixed <- renderText({
        req(text_in_new())
      })
    }
    
    shinyApp(ui = ui, server = server)