” Error: object ‘output’ not found ” returned from downloadHandler reactive table in R Shiny

I am working on a side project for a budget template as a Shiny app. I have everything else worked out, but I’m getting stuck on implementing a downloadButton and downloadHandler feature to download the transaction table (Table 2) from the main panel as a CSV file. I think my biggest issue here is that I’m working with a reactive table where I can add and remove rows at whim. I watched several YouTube videos and read as many SO questions as I could find, but I’m not making anymore headway. The best I could try was adding another renderTable function right before the downloadHandler. Also, I noticed that changing tableOutput to dataTableOutput (avoided since deprecated) or renderDT completely removes table1 and table2 when the app is ran. Could this be related? What can I do to fix this issue? I just started using Shiny this week, so all help is greatly appreciated. Thanks!

Below is my code:

##### Necessary packages. #####
library(shiny)
if(!require(shinyjs)) { install.packages("shinyjs")}
if(!require(shinyWidgets)) { install.packages("shinyWidgets")}
library(tidyverse)
if(!require(writexl)) { install.packages("writexl")}

##### Creating the app. #####

# Custom functions to add a delete button to each transaction table row.
# Define UI for application that creates an interactive budget. 
ui <- fluidPage(

# Application title
    titlePanel(h1("50-30-20 Interactive Budget", align = "center")),

# Sidebar with fields for income and transaction information and buttons to
# reset information or add it to the table. Also includes table displaying
# budget budget balances.
    sidebarLayout(
        sidebarPanel(
          shinyjs::useShinyjs(),
          id = "side_panel",
          shinyWidgets::autonumericInput(
            "inc",
            "Enter your monthly income:",
            min = 0,
            value = 1000.00,
            decimalPlaces = 2
            ),
          actionButton("button", "Reset Monthly Income"),
          tableOutput("table1"),
          tags$hr(),
            
          dateInput(
            "date", 
            "Enter the transaction date:", 
            format = "mm-dd-yyyy",
            value = "06-25-2024"
              ),
          textInput("desc", "Enter the transaction description:"),
          selectInput(
            "type",
            "Select the transaction category:",
            choices = c("Bills", "Discretionary", "Savings")
            ),
          shinyWidgets::autonumericInput(
            "amt",
            "Enter the transaction amount:",
            min = 0,
            value = 0.00,
            decimalPlaces = 2
          ),
          actionButton("button2", "Reset Transaction Information"),
          actionButton("button3", "Add Information to Table"),
          actionButton("button4", "Remove Previous Row"),
          downloadButton("downloadData","Download Tables")
        ),
    
# Display an explanation of the budget and display the transaction table.
        mainPanel(
           p("The 50-30-20 budget divides monthly income into categories of 50% 
             of income for bills and fixed expenses, 30% for discretionary 
             purchases, and 20% for savings. Please enter your monthly income 
             and the transaction date, description, type (Bills, Discretionary, 
             or Savings), and amount below. The app will calculate each 
             category's starting and remaining monthly balance. Resetting
             the app will clear all information."),

           
           tableOutput("table2")
        )
    )
)

# Define server logic required to render fields, buttons, and tables.
server <- function(input, output, session) {
  


# Assign functionality to transaction table. Since button3 and button4 directly
# affect the xyTable, assign functionality here instead of with the other buttons.
  xyTable <- reactiveVal(
    tibble(Date = ymd(), Description = character(), Category = character(), Amount = numeric())
  )
  
  observeEvent(input$button3, {
    xyTable() %>%
      add_row(
        Date = input$date,
        Description = input$desc,
        Category = input$type,
        Amount = input$amt
      ) %>%
      xyTable()
  }
  )
  
  observeEvent(input$button4,{ xyTable() %>% filter(row_number() < n()) %>% xyTable()})

  output$table2 <- renderTable(xyTable() %>% mutate(Date = format(Date, "%m-%d-%Y")), width = "auto")  
  
# Assign functions to the buttons.  
  observeEvent(input$button, {shinyjs::reset("inc")})
  observeEvent(input$button2,{
    updateDateInput(session, "date", value = "06-25-2024")
    updateTextInput(session, "desc", value = "")
    updateSelectInput(session, "type", choices = c("Bills", "Discretionary", "Savings"))
    updateAutonumericInput(session, "amt", value = 0.00)
  })

# Create the starting and remaining balance columns.
  output$table1 <- renderTable(
    tibble(
      Category = c("Bills", "Discretionary", "Savings"),
      `Starting Balance` = c(input$inc * 0.5, input$inc * 0.3, input$inc * 0.2)
        
      )
  )

}



# Write tables to Excel file for download button.
output$table2 <- renderTable({T2()})

output$downloadData <- downloadHandler(
  filename = "50-30-20Budget.csv",
  content = function(file) {
    write.csv(T2(), file)
  }
)

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