Modularised code with tabPanels ... ?

I'm trying modularising code. I am trying to generate a viable number of categories, with a variable number of rows. Each row has txtOutputs and actionButtons

This first example with actionLinks for the categories works (see https://ikusei.shinyapps.io/IkuseiBoK/. Trying doing the same with tabPanels instead of actionLinks is doing my head in (see code below).

Any help would be appreciated

WORKING CODE

#
#This is a Shiny web application. You can run the application by clicking
#the 'Run App' button above.
#
#Find out more about building applications with Shiny here:
#
#http://shiny.rstudio.com/
#

library('shiny')
library("htmltools")
library("vembedr")
library("RSQLite")
library("shinyjs")
library("V8")

jsResetCode <- "shinyjs.reset = function() {history.go(0)}" #Define the js method that resets the page

#open db
db <- dbConnect(SQLite(),"data/BoK.db")
#Initial category id
catid <- 1

#######MODULES

gen_tabsUI <- function(id, users_cats, i) {
  ns <- NS(id)

  column(2, tags$span(actionLink(ns("lnkCat"),paste0(users_cats[i,2])) ),align="center")
}
  

gen_tabs <- function(input, output, session, users_cats, i) {
  
  observeEvent(input$lnkCat, {
    
    catid <<- users_cats[i, 4]
    js$reset()
  })
}

#UI part of module gen_rows
#generate the fluidRow
gen_rowsUI <- function(id, users_drills, no_cats, users_cats) {
  ns <- NS(id)

  #generate the fluidRow
  fluidPage(
    fluidRow(
        #drill title
      column(12, tags$p(textOutput(ns("txtTitle"))))
    ),
    fluidRow(
      #drill weighting
      column(6, 
             #drill reps
             tags$p("Completed ",
                    actionButton(ns("btnDec"), label=NULL, icon("minus") ,36),
                    textOutput(ns("txtReps"), inline = TRUE),
                    actionButton(ns("btnInc"), label=NULL, icon("plus") ,36),
                    "Times"),
             align = "left"),
      column(6, tags$p("Weighting is",
                       actionButton(ns("btnDecW"), label=NULL, icon("minus") ,36),
                       textOutput(ns("txtWeight"), inline = TRUE),
                       actionButton(ns("btnIncW"), label=NULL, icon("plus") ,36),
                       align = "right")
      )
    ),
    
    hr()
  )
}

#Server part of module gen_rows
gen_rows <- function(input, output, session, users_drills, indx, no_cats, users_cats) {

  reps <- users_drills[indx, 1]
  title <- users_drills[indx, 2]
  weight <- users_drills[indx, 3]
  prio <- users_drills[indx, 4]
  drill_order <- users_drills[indx, 5]
  
  output$txtReps <- renderText(reps)
  output$txtTitle <- renderText(paste(title, prio, sep = " - "))
  output$txtWeight <- renderText(weight)

  
  observeEvent(input$btnInc,{
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)

    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    #add 1 to the reps
    reps <- reps + 1
    #recalculate priority
    prio <- ((weight)/(reps + 1)) * 1000000
    
    #render reps and title
    output$txtReps <- renderText(reps)
    output$txtTitle <- renderText(paste(title, prio, sep = " - "))

    temp <- drill_order
    
    #update database
    qry_string  <- paste0("UPDATE users_drills SET reps = ", reps, ", prio = ", prio, " 
                          WHERE userid = 1 AND [order] = ", drill_order)
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
    
  })
  
  observeEvent(input$btnDec,{
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)
    
    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    
    if (reps > 0){
      reps <- reps - 1
    }
    prio <- ((weight)/(reps + 1)) * 1000000
    
    #users_drills[indx, 2] <- reps
    #users_drills[indx, 5] <- prio
    
    output$txtReps <- renderText(reps)
    output$txtTitle <- renderText(paste(title, prio, sep = " - "))
    
    #update database
    
    qry_string  <- paste0("UPDATE users_drills SET reps = ", reps, ", prio = ", prio, " 
                          WHERE userid = 1 AND [order] = ", drill_order)
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
    
  })
  
  observeEvent(input$btnIncW,{
    
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)
    
    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    
    if (weight >= 5) {
      weight = 5
      } else {
      weight <- weight + 1
      }
    
      prio <- ((weight)/(reps + 1)) * 1000000
      
      #users_drills[indx, 4] <- weight
      #users_drills[indx, 5] <- prio
      
      output$txtWeight <- renderText(weight)
      output$txtTitle <- renderText(paste(title, prio, sep = " - "))
      
      #update database
      
      qry_string  <- paste0("UPDATE users_drills SET weight = ", weight, ", prio = ", prio, " 
                            WHERE userid = 1 AND [order] = ", drill_order)
      qry_users_drills <- dbSendQuery(db, qry_string)
      dbClearResult(qry_users_drills)
  })  

  observeEvent(input$btnDecW,{
    
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)

    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    if (weight <= 0) {
      weight = 0
    } else {
      weight <- weight - 1
    }
    prio <- ((weight)/(reps + 1)) * 1000000
    
    #users_drills[indx, 4] <- weight
    #users_drills[indx, 5] <- prio
    
    output$txtWeight <- renderText(weight)
    output$txtTitle <- renderText(paste(title, prio, sep = " - "))
    
    #update database
    
    qry_string  <- paste0("UPDATE users_drills SET weight = ", weight, ", prio = ", prio, " 
                          WHERE userid = 1 AND [order] = ", drill_order)
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
  })  
  
    
}


#Define UI for Drill Browser
ui <- fluidPage(
  
  useShinyjs(),
  extendShinyjs(text = jsResetCode),                      #Add the js code to the page
  
  #Application title
  titlePanel("Ikusei Body of Knowledge"),

      #Header row
      fluidRow(
        column(6, tags$h4("Exercises")),
        column(6, actionButton("btnRefresh", "Refresh", NULL, width = 96), align = "right")
      ),
      fluidRow(
       uiOutput("Cats")
      ),
  #Generate list of drills dynamically
  uiOutput("listofDrills")
)
      

#Define server logic
server <- function(input, output) {
  
  #obtain list of drills and put in to a data frame
  qry_users_drills <- dbSendQuery(db,"SELECT drillid, reps, title, weight, prio, [order], catid
                                  FROM `drills` INNER JOIN users_drills 
                                  ON drills.id = users_drills.drillid 
                                  WHERE userid = 1 ORDER BY prio DESC, reps DESC")
  #data frame 'users_drills'
  users_drills <- dbFetch(qry_users_drills, n = -1)
  dbClearResult(qry_users_drills)
  
  #obtain no of drills (rows)
  drills_no <- nrow(users_drills)
  
  for(i in 1:drills_no) {
    reps <- users_drills[i, 2]
    weight <- users_drills[i, 4]
    prio <- users_drills[i, 5]
    prio <- ((weight)/(reps + 1)) * 1000000
    qry_string  <- paste0("UPDATE users_drills SET [order] = ", i, ", [prio] = ", prio,
                          " WHERE userid = 1 AND drillid = ", users_drills[i, 1])
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
  }

  #requery database so datframe ('user_cats') has latest data
  qry_users_cats <- dbSendQuery(db, "SELECT title, display, [order], id FROM cats INNER JOIN users_cats
                                ON cats.id = users_cats.catid WHERE userid = 1 ORDER BY [order]")
  users_cats <- dbFetch(qry_users_cats, n = -1)
  dbClearResult(qry_users_cats)
  
  #obtain number of categories
  no_cats <- nrow(users_cats)
  
  #Generate a tab for every category in the "users_drills" table
  observeEvent(no_cats, {

    output$Cats <- renderUI({
      #for each cat create a tab
      lapply (1:no_cats, function(i) {
        gen_tabsUI(i, users_cats, i)
      })
    })

    lapply(1:no_cats, function(i) {

      callModule(gen_tabs, i, users_cats, i)
    })
  })

  #requery database so datframe ('user_drills') has latest data
  qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid
                                  FROM `drills` INNER JOIN users_drills
                                  ON drills.id = users_drills.drillid
                                  WHERE userid = 1 AND catid = ", users_cats[catid, 3] ," ORDER BY [order]"))
  users_drills <- dbFetch(qry_users_drills, n = -1)
  dbClearResult(qry_users_drills)

  #obtain no of drills (rows)
  no_drills <- nrow(users_drills)

  #Generate a row for every drill in the "users_drills" table
  observeEvent(no_drills, {

    output$listofDrills <- renderUI({
      
      if (no_drills > 0) {

        #for each drill create fluidRow
        lapply (1:no_drills, function(i) {
          gen_rowsUI(i, users_drills, no_cats, users_cats)
        })
      }
    })

    lapply(1:no_drills, function(i) {

      callModule(gen_rows, i, users_drills, i, no_cats, users_cats)
    })
  })
  
  observeEvent(input$btnRefresh, {
    
    js$reset()
        
  })
}

#Run the application 
shinyApp(ui = ui, server = server)

BAD CODE

#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
#    http://shiny.rstudio.com/
#

library('shiny')
library("htmltools")
library("vembedr")
library("RSQLite")
library("shinyjs")
library("V8")

jsResetCode <- "shinyjs.reset = function() {history.go(0)}" # Define the js method that resets the page

#open db
db <- dbConnect(SQLite(),"data/BoK.db")
# Initial category id
catid <- 1

users_drills <- NULL

####### MODULES

# UI part of module gen_rows
gen_rowsUI <- function(id) {
  ns <- NS(id)

  div(
      # drill title
      column(12,
             div(tags$p(textOutput(ns("txtTitle"))))
             ),
      # drill weighting
      column(6,
             # drill reps
             tags$p("Completed ",
                    actionButton(ns("btnDec"), label=NULL, icon("minus") ,36),
                    textOutput(ns("txtReps"), inline = TRUE),
                    actionButton(ns("btnInc"), label=NULL, icon("plus") ,36),
                    "Times"),
             align = "left"),
      
      column(6, 
             # drill weighting
             tags$p("Weighting is",
                       actionButton(ns("btnDecW"), label=NULL, icon("minus") ,36),
                       textOutput(ns("txtWeight"), inline = TRUE),
                       actionButton(ns("btnIncW"), label=NULL, icon("plus") ,36)),
             align = "right"),
    hr()
  )
}

# Server part of module gen_rows
gen_rows <- function(input, output, session, users_drills, indx, no_cats, users_cats) {

  reps <- users_drills[indx, 1]
  title <- users_drills[indx, 2]
  weight <- users_drills[indx, 3]
  prio <- users_drills[indx, 4]
  drill_order <- users_drills[indx, 5]
  
  output$txtReps <- renderText(reps)
  output$txtTitle <- renderText(paste(title))# , prio, sep = " - "))
  output$txtWeight <- renderText(weight)

  
  observeEvent(input$btnInc,{
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <<- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)

    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    # add 1 to the reps
    reps <- reps + 1
    # recalculate priority
    prio <- ((5 - weight)/(reps + 1)) * 1000000
    
    # render reps and title
    output$txtReps <- renderText(reps)
    output$txtTitle <- renderText(paste(title))#, prio, sep = " - "))

    temp <- drill_order
    
    #update database
    qry_string  <- paste0("UPDATE users_drills SET reps = ", reps, ", prio = ", prio, " 
                          WHERE userid = 1 AND [order] = ", drill_order)
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
    
  })
  
  observeEvent(input$btnDec,{
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <<- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)
    
    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    
    if (reps > 0){
      reps <- reps - 1
    }
    prio <- ((5 - weight)/(reps + 1)) * 1000000
    
    # users_drills[indx, 2] <- reps
    # users_drills[indx, 5] <- prio
    
    output$txtReps <- renderText(reps)
    output$txtTitle <- renderText(paste(title))#, prio, sep = " - "))
    
    #update database
    
    qry_string  <- paste0("UPDATE users_drills SET reps = ", reps, ", prio = ", prio, " 
                          WHERE userid = 1 AND [order] = ", drill_order)
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
    
  })
  
  observeEvent(input$btnIncW,{
    
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <<- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)
    
    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    
    if (weight >= 5) {
      weight = 5
      } else {
      weight <- weight + 1
      }
    
      prio <- ((5 - weight)/(reps + 1)) * 1000000
      
      # users_drills[indx, 4] <- weight
      # users_drills[indx, 5] <- prio
      
      output$txtWeight <- renderText(weight)
      output$txtTitle <- renderText(paste(title))#, prio, sep = " - "))
      
      #update database
      
      qry_string  <- paste0("UPDATE users_drills SET weight = ", weight, ", prio = ", prio, " 
                            WHERE userid = 1 AND [order] = ", drill_order)
      qry_users_drills <- dbSendQuery(db, qry_string)
      dbClearResult(qry_users_drills)
  })  

  observeEvent(input$btnDecW,{
    
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid 
                                    FROM `drills` INNER JOIN users_drills
                                    ON drills.id = users_drills.drillid
                                    WHERE userid = 1 AND catid = ", catid ," ORDER BY [order]"))
    users_drills <<- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)

    reps <- users_drills[indx, 1]
    title <- users_drills[indx, 2]
    weight <- users_drills[indx, 3]
    prio <- users_drills[indx, 4]
    drill_order <- users_drills[indx, 5]
    
    if (weight <= 0) {
      weight = 0
    } else {
      weight <- weight - 1
    }
    prio <- ((5 - weight)/(reps + 1)) * 1000000
    
    # users_drills[indx, 4] <- weight
    # users_drills[indx, 5] <- prio
    
    output$txtWeight <- renderText(weight)
    output$txtTitle <- renderText(paste(title))#, prio, sep = " - "))
    
    #update database
    
    qry_string  <- paste0("UPDATE users_drills SET weight = ", weight, ", prio = ", prio, " 
                          WHERE userid = 1 AND [order] = ", drill_order)
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
  })  
  
    
}


# Define UI for Drill Browser
ui <- fluidPage(
  
  useShinyjs(),
  extendShinyjs(text = jsResetCode),                      # Add the js code to the page
  
  # Application title
  titlePanel("Ikusei Body of Knowledge"),

  # Header row
  fluidRow(
    column(6, tags$h4("Exercises")),
    # column(3, actionButton("run", "Run Analysis", icon("paper-plane"), 
    #              style="color: #fff; background-color: #337ab7; border-color: #2e6da4")),    
    column(3, actionButton("btnRefresh", "Refresh", NULL, width = 96), align = "right", offset = 3)
  ),

    tabsetPanel(
      tabPanel("default"),
      # tabPanel("default2"),
      #   # Generate list of drills dynamically
      # div(uiOutput("listofDrills")),
      id ="Cats", selected = NULL, type = "pills")
)

      

# Define server logic
server <- function(input, output) {
  
  # obtain list of drills and put in to a data frame
  qry_users_drills <- dbSendQuery(db,"SELECT drillid, reps, title, weight, prio, [order], catid
                                  FROM `drills` INNER JOIN users_drills 
                                  ON drills.id = users_drills.drillid 
                                  WHERE userid = 1 ORDER BY prio ASC, reps DESC")
  # data frame 'users_drills'
  users_drills <<- dbFetch(qry_users_drills, n = -1)
  dbClearResult(qry_users_drills)
  
  #obtain no of drills (rows)
  drills_no <- nrow(users_drills)
  
  for(i in 1:drills_no) {
    reps <- users_drills[i, 2]
    weight <- users_drills[i, 4]
    prio <- users_drills[i, 5]
    prio <- ((5 - weight)/(reps + 1)) * 1000000
    qry_string  <- paste0("UPDATE users_drills SET [order] = ", i, ", [prio] = ", prio,
                          " WHERE userid = 1 AND drillid = ", users_drills[i, 1])
    qry_users_drills <- dbSendQuery(db, qry_string)
    dbClearResult(qry_users_drills)
  }

  #requery database so datframe ('user_cats') has latest data
  qry_users_cats <- dbSendQuery(db, "SELECT title, display, [order], id FROM cats INNER JOIN users_cats
                                ON cats.id = users_cats.catid WHERE userid = 1 ORDER BY [order]")
  users_cats <- dbFetch(qry_users_cats, n = -1)
  dbClearResult(qry_users_cats)
  
  # obtain number of categories
  no_cats <- nrow(users_cats)
  
  for (i in 1:no_cats) {
    #requery database so datframe ('user_drills') has latest data
    qry_users_drills <- dbSendQuery(db, paste0("SELECT reps, title, weight, prio, [order], catid
                                               FROM `drills` INNER JOIN users_drills
                                               ON drills.id = users_drills.drillid
                                               WHERE userid = 1 AND catid = ", users_cats[i, 4] ," ORDER BY [order]"))
    users_drills <<- dbFetch(qry_users_drills, n = -1)
    dbClearResult(qry_users_drills)
    
    #obtain no of drills (rows)
    no_drills <- nrow(users_drills)
    
    a <- users_cats[i, 3]

    # output$listofDrills <- renderUI({
    appendTab(inputId = "Cats",
              tabPanel(users_cats[i,2],

                        # if (no_drills > 0) {
                        #   # for each drill create fluidRow
                        lapply (1:(no_drills/5), function(d) {
                          gen_rowsUI(d)
                        }),
                        # }
                        b <- i,
                        lapply(1:no_drills, function(d) {
                          callModule(gen_rows, d, users_drills, d, no_cats, users_cats)
                        })
                       
                        , paste0(i)
              )
    )

  }
  
  # # Generate a tab for every category in the "users_drills" table
  # observeEvent(no_cats, {
  # 
  #   output$Cats <- renderUI({
  #     # for each cat create a tab
  #     lapply (1:no_cats, function(i) {
  #       gen_tabsUI(i, users_cats, i)
  #     })
  #   })
  # 
  #   lapply(1:no_cats, function(i) {
  # 
  #     callModule(gen_tabs, i, users_cats, i)
  #   })
  # })

  observeEvent(input$btnRefresh, {
    
    js$reset()
        
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

I looked at your link, and I think the problem is caused by duplicated identifiers. When you use action links you get a different html page for each link target, but when you use tabPanels, all html for all the tab content is generated on one big html page. The way you construct the dynamic parts of the page you use the same identifiers on the different pages/tabPanels, and that won't work - html requires identifiers to be absolutely unique across the entire html file. One example is the tilte of the drills, here you use txtTitle as identifier, modulized by ns, so the id in the html becomes "1-txtTitle" for the first entry. But this will be the same for all the tabPanels, they will all contain an html element with the id "1-txtTitle", hence the problem.

You could perhaps include the tabPanel title in the id that you pass to gen_rowUI so the first entry on the first tab would become "Drills_1-txtTitle", or something similar.

This is a general challenge when using tabPanels that all id across all tabPanels have to be unique.

Cheers
Steen

Thanks Steen. I will play around and see what I can do.

All other suggestions are welcome.

I haven't looked through your code, but if this is a problem you find yourself experiencing, Shiny Modules were designed to remedy exactly this type of namespacing problem. If "modularizing" your code is your objective, you can certainly also combine Shiny Modules and tabPanels. I think the Shiny community could definitely do with more blog posts and buzz around this type of paradigm :slight_smile:

1 Like

Hi Cole. Yes I am using shiny modules, but if this is possible with tabPanels, I must be doing something fundamentally wrong. I will keep trying, even if it takes stripping it back to building a basic proof of concept app.

Cheers.

Interesting! Thanks for following up - you don't need to strip your app down, but sometimes it can be helpful (for you and for others) if you can provide a simpler example for us to poke at with you? (i.e. a self-contained "reprex") I have to admit the number of lines in your app intimidates me a bit :slight_smile: (It probably has lots of context I would need to absorb before anything I say would be helpful). If you can create a more minimal example, that might help us make some progress with you! (Speaking from experience - sometimes starting the minimal example helps illustrate the proper pattern to myself as well)

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