Need to load a saved parameter file twice in order to update the field from uiOutput/insertUI

I am new to shiny and apologize for the naive question of updating uiOutput/insertUI. In my setup, I first saved a parameter file at "Enzyme = Special" and "Value = 10". I next closed and reopened the app and reloaded the parameter file. I need to do the loading twice in order to update correctly the uiOutput field. My suspect is that the first loading updated the dynamic UI and the second updated subsequently the ""Value". Would there be a way to update all the fields at once. Your helps on this would be greatly appreciated. My snippet is shown below.

library(shiny)
library(shinyFiles)
library(fs)

myUI <- function(id, choices = c("Default", "Special"))
{
  tagList(
    selectInput(NS(id, "enzyme"), "Enzyme", choices, selected = "Default"),
    uiOutput(NS(id, "with_special")),
  )
}


myServer <- function(id)
{
  moduleServer(id,
               function(input, output, session) {
                 enzyme <- reactive(input$enzyme)
                 
                 observe({
                   if (enzyme() == "Special")
                     output$with_special <- renderUI(numericInput(NS(id, "more"), "Value", value = 0))
                 })
                 
                 list(enzyme = reactive(input$enzyme), more = reactive(input$more))
               }
  )
}


ui <- fluidPage(
  myUI("MID"),
  shinySaveButton("savepars", "Save parameters", "Save file",
                  filetype = list(text = "pars"), viewtype = "icon"),
  shinyFilesButton("loadpars", "Load parameters", "Please select a file",
                   filetype = list(text = "pars"), multiple = FALSE, viewtype = "detail"),
)


server <- shinyServer(function(input, output, session) {
  volumes <- c(Home = fs::path_home(), getVolumes()())
  
  ans <- myServer("MID")
  enzyme <- ans$enzyme
  more <- ans$more
  pars <- reactive(list(enzyme = enzyme(), more = more()))
  
  observeEvent(input$savepars, {
    shinyFileSave(input, "savepars", roots = volumes, session = session,
                  restrictions = system.file(package = "base"))
    fileinfo <- parseSavePath(volumes, input$savepars)
    
    if (nrow(fileinfo))
      saveRDS(pars(), fileinfo$datapath)
  })
  
  observeEvent(input$loadpars, {
    shinyFileChoose(input, "loadpars", roots = volumes, session = session)
    
    if (is.integer(input$loadpars))
      NULL
    else {
      fileinfo <- parseFilePaths(volumes, input$loadpars)
      fileinfo <- gsub("\\\\", "/", unname(fileinfo[["datapath"]]))
      cached_pars <- readRDS(fileinfo)
      updateSelectInput(session,  NS("MID", "enzyme"), "Enzyme", choices = c("Default", "Special"),
                        selected = cached_pars$enzyme)
      updateNumericInput(session, NS("MID", "more"), "Value", value = cached_pars$more)
    }
  })
})


shinyApp(ui, server)

to me

updateSelectInput(session,  NS("MID", "enzyme"), "Enzyme", choices = c("Default", "Special"),
                        selected = cached_pars$enzyme)
      updateNumericInput(session, NS("MID", "more"), "Value", value = cached_pars$more)

in your apps server code, largely defeats the purpose of modules, the module needs appropriate inputs with which a calling app can ask it to update itself, the update codes need to be within the module server code not the app server code in my opinion.

@nirgrahamuk , Thank you for the comment. The problem of two loadings to update properly uiOutput/insertUI from a parameter file seems generic for any instance... Still not sure how to solve this. (As an aside, I have multiple modules in my app and each has its own update/reset utilities. The grand update[...]s from the server side are to update the parameters for all modules upon app re-launching.)

I've abstracted away from your initial setup to try to get down to the essentials. which is that you cant update what hasnt been created, and you cant guarantee the exact timings of something that is being created as result of another thing in such a way that you can simultaneously update them. So one possible approach (there may be others also) is to always produce the thing that you only want sometimes; but choose when to show it.
that way it is always present to be updated, even if the user may not be aware of it. as in this example

library(shiny)

ui <- fluidPage(
  actionButton("forcebtn", "force"),
  selectInput("fixed", label = "fixed", choices = c("default", "something")),
  uiOutput("dynamic")
)

server <- function(input, output, session) {
  output$dynamic <- renderUI({
    content <- numericInput("inner_dyn",
      label = "inner_dyn",
      min = 0, max = 10, value = dplyr::coalesce(input$inner_dyn, 0)
    )

    # dont make **creating** it conditional;
    # make showing it conditional
    if (input$fixed != "something") {
      content <- div(
        style = "visibility:hidden;",
        content
      )
    }
    content
  })

  observeEvent(input$forcebtn, {
    updateSelectInput(
      session = session,
      inputId = "fixed",
      selected = "something"
    )
    updateNumericInput(
      session = session,
      inputId = "inner_dyn",
      value = 6
    )
  })
}

shinyApp(ui, server)

Interesting idea of content hiding. I will give it a go.

Tried the hiding trick and still ran into the two-loading situation. One caveat of the hiding approach is that it creates white space in the UI.

library(shiny)
library(shinyFiles)

myUI <- function(id, choices = c("Default", "Special"))
{
  tagList(
    selectInput(NS(id, "enzyme"), "Enzyme", choices, selected = "Default"),
    uiOutput(NS(id, "at_special")),
  )
}


myServer <- function(id)
{
  moduleServer(id,
               function(input, output, session) {
                 output$at_special <- renderUI({
                   x <- numericInput(NS(id, "more"), "Value", value = 0)
                   
                   if (input$enzyme != "Special") {
                     x <- div(
                       style = "visibility:hidden;",
                       x)
                   }
                   
                   x
                 })
                 
                 list(enzyme = reactive(input$enzyme), valmore = reactive(input$more))
               }
  )
}


ui <- fluidPage(
  myUI("MID"),
  shinySaveButton("savepars", "Save parameters", "Save file",
                  filetype = list(text = "pars"), viewtype = "icon"),
  shinyFilesButton("loadpars", "Load parameters", "Please select a file",
                   filetype = list(text = "pars"), multiple = FALSE, 
                   viewtype = "detail"),
)


server <- shinyServer(function(input, output, session) {
  volumes <- c(Home = "~", getVolumes()())
  
  ans <- myServer("MID")
  enzyme <- ans$enzyme
  valmore <- ans$valmore
  pars <- reactive(list(enzyme = enzyme(), valmore = valmore()))
  
  observeEvent(input$savepars, {
    shinyFileSave(input, "savepars", roots = volumes, session = session,
                  restrictions = system.file(package = "base"))
    fileinfo <- parseSavePath(volumes, input$savepars)
    
    if (nrow(fileinfo))
      saveRDS(pars(), fileinfo$datapath)
  })
  
  observeEvent(input$loadpars, {
    shinyFileChoose(input, "loadpars", roots = volumes, session = session)
    
    if (is.integer(input$loadpars))
      NULL
    else {
      fileinfo <- parseFilePaths(volumes, input$loadpars)
      fileinfo <- gsub("\\\\", "/", unname(fileinfo[["datapath"]]))
      cached_pars <- readRDS(fileinfo)
      
      updateSelectInput(session,  NS("MID", "enzyme"), "Enzyme", choices = c("Default", "Special"),
                        selected = cached_pars$enzyme)
      updateNumericInput(session, NS("MID", "more"), "Value", value = cached_pars$valmore)
    }
  })
})


shinyApp(ui, server)

theres a subtlety here. in your moduleServer, output$at_special <- renderUI will get run any time input$enzyme changes; and this forces more to be zero. (value = 0) therefore when loading the params from file, if enzym is not special, the first time the params are loaded the more wont be set, (or to be more precise it will perhaps be set but then immediately forced to zero) ; and it will need a second load to adjust it.
The solution is

      x <- numericInput(NS(id, "more"), "Value", value = dplyr::coalesce(input$more,0))
         

to attempt to preserve the setting of more when adjusting it due to a change in Special.

I had similar in my example

value = dplyr::coalesce(input$inner_dyn, 0)

You are right. It now works as intended with your additional note. Thank you very much for your help.

This topic was automatically closed 54 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.