Cannot access DT state from within a Shiny module

I am trying to write a Shiny app that only updates ONLY the data that is presented to the user by DT. I can do this outside of a Shiny module (below), but when I wrap this code in a Shiny module I lose the ability to query the state of the table.

How can I access the state of a DT once it's been wrapped in a module?

This is the non-Shiny module code that works.

#' This example is based on the Github issue #359
#' https://github.com/rstudio/DT/issues/359

#' problem statement: cannot access the state of a DT to 
#' update only the rows which the user is currently viewing
#' and wrap this function in a module

#' credit: Aaron Berg <aaron@rstudio.com> for getting this to 
#' work properly
library(shiny)
library(DT)
library(dplyr)
shinyApp(
  ui = fluidPage(
    fluidRow(
      column(2, actionButton('upper', 'Upper Case')),
      column(12, DT::dataTableOutput('foo'))
    ),
    fluidRow(
      column(12, verbatimTextOutput('info'))
    )
  ),
  
  server = function(input, output, session) {
    df <- tibble(letter = letters)
    n = nrow(df)
    df$ID = seq_len(n)
    
    loopData = reactive({
      input$upper
      #' print stuff to the console
      print(isolate(input$foo_state$start))
      print(isolate(input$foo_state$length))
      
      if(is.null(isolate(input$foo_state$length))){
        start <- 0
        end <- 0
      } else{
        start <- (isolate(input$foo_state$start) + 1)
        end <- isolate(input$foo_state$start + 
                         ifelse(is.null(input$foo_state$length),
                                1, 
                                input$foo_state$length))
        if (end >= n) {
          # prints end of the current users DT
          cat(paste("end of current frame: ", end, "\n"))
          end <- start + (n %% start)
        }
      }
      df$letter[start:end] <<- toupper(df$letter[start:end])
      df
    })
    observeEvent(input$foo_state, {
      output$info = renderPrint(str(input$foo_state))
    })
    output$foo = DT::renderDataTable(datatable(isolate(loopData()), 
                                               options = list(stateSave = TRUE)))
    proxy = dataTableProxy('foo')
    observe({
      replaceData(proxy, loopData(), resetPaging = FALSE)
    })
  }
)

This is the attempt at a Shiny module that does not work.

#' This example is based on the Github issue #359
#' https://github.com/rstudio/DT/issues/359

#' problem statement: cannot access the state of a DT to 
#' update only the rows which the user is currently viewing
#' and wrap this function in a module

#' credit: Aaron Berg <aaron@rstudio.com> for debug help

#' THIS EXAMPLE DOES NOT WORK
#' Comments inline
library(shiny)
library(DT)
library(tidyverse)

modUI <- function(id) {
  ns <- NS(id)
  fluidPage(
    fluidRow(
      column(2, actionButton(ns('upper'), 'Upper Case')),
      column(10, DT::dataTableOutput(ns('foo')))
    )
  )
}

modServer <- function(input, output, session) {
  df <- tibble(letter = letters)
  n = nrow(df)
  df$ID = seq_len(n)
  
  loopData = reactive({
    input$upper
    
    #' Looping update of ID columne works, 
    #' but is not filtering on what the user views
    df$ID <<- c(df$ID[n], df$ID[-n]) 
   
    #' however the state is NULL
    print(isolate(input$foo_state$start))
    print(isolate(input$foo_state$length))
    
    if(is.null(isolate(input$foo_state$length))){
      start <- 0
      end <- 0
    } else{
      start <- (isolate(input$foo_state$start) + 1)
      end <- isolate(input$foo_state$start + ifelse(is.null(input$foo_state$length), 1, input$foo_state$length))
    }
    
    df$letter[start:end] <<- toupper(df$letter[start:end])
    
    df
  })
  
  output$foo = DT::renderDataTable(isolate(loopData()))
  
  proxy = dataTableProxy('foo')

  observe({
    replaceData(proxy, loopData(), resetPaging = FALSE)
  })
}

shinyApp(
  ui = modUI('module'),
  server = function(input, output, session) callModule(modServer, 'module')
)

You simply forgot to set the option options = list(stateSave = TRUE) in the example with the module. Then (at least with the current version of DT) it works.

Mind that the first time the state is printed, it is still NULL because it hasn't yet been initialized.