<p>For various reasons I want to be able to use a proxied data table and replaceData while client side processing is being used i.e. <code>DT::renderDataTable(..., server = FALSE)</code>.</p>
<h2>Context</h2>
<p>I have a shiny application/dashboard that communicates to a database and presents information to a user. The user is able to fill out a form in the application which will be added to the database and then the shiny app updates the data by making a query to the database to fetch the new information.</p>
<p>The application is currently being deployed via kubernetes using a <code>LoadBalancer</code> with the intention to use multiple replicas to scale up the application as needed. The application is not being run through shinyproxy.</p>
<h2>Caveats</h2>
<p>Currently, when the application is being run by a single replica (process) the application will behave perfectly fine and is able to use <code>server=TRUE</code>. However when I increase the number of processes/replicas to run, the data is not able to be presented to users unless <code>server=FALSE</code> is specified in <code>renderDataTable</code>. For a currently unknown reason but I suspect it might be due to the sessions not being sticky to IPs</p>
<p>While the code is able to function fine when <code>server = TRUE</code> if I want to allow multiple users to application they all cannot share a single process as the application will become very slow once multiple connections are made. As a result I likely need to use <code>server=FALSE</code> so each user is able to see the data at the cost of a very important functional detail (<code>replaceData</code> stops working). The product owner of the application is insistent that this behaviour remains intact as the data present is often large and requires some column sorting and paging to find a piece of information you want to look at. And when submitting a form, if I do not use <code>replaceData</code> and reconstruct the table from scratch the users previous table state is lost.</p>
<p>So while I could tear down the datatable and regenerate it within an <code>observeEvent</code></p>
<pre><code>observeEvent(input$button, {
    ...
    output$table = renderDataTable({DT::datatable(df(), selection = 'single', callback = 
    JS(&quot;$.fn.dataTable.ext.errMode = 'none';&quot;))}, server = FALSE)
    ...
})
</code></pre>
<p>this would provide a solution that would yield unfavourable behaviour even though it will update the table accordingly.</p>
<h2>Repoducible Example</h2>
<p>This will create an application with a button and a table. Select a row on the table and then click the button. The expected behaviour would be that the table updates with 'new_content' on the row that is selected. This will only work when <code>server=TRUE</code>, nothing will happen when <code>server=FALSE</code>.</p>
<pre><code>library(shiny)
library(DT)
data(iris)

server &lt;- function(input, output, session) {
  iris$new_col = ''
  df = reactive({iris})
  output$table = renderDataTable({
      DT::datatable(df(), selection = 'single', 
        callback = JS(&quot;$.fn.dataTable.ext.errMode = 'none';&quot;))}, server = FALSE) # When TRUE code works fine,,,
  proxy = dataTableProxy('table')

  observeEvent(input$button, {
    # This line would be replacing the write to a db
    iris[input$table_rows_selected, 'new_col'] &lt;- 'changed'
    # This line would be replacing the query to the db to reflect changes the user (and potentially other users have made between loading the data previously.
    df &lt;- reactive({iris})
    proxy %&gt;% replaceData(df(), rownames = TRUE, resetPaging = FALSE)
  })
}
    
ui &lt;- fluidPage(
  actionButton('button', 'Press Me'),
  DT::DTOutput('table') 
)

shinyApp(ui, server)
</code></pre>
<p>I have done a fairly extensive search on SO and this was the closest question I could find: <a href="https://stackoverflow.com/questions/52519367/dt-editing-in-shiny-application-with-client-side-processing-server-f-throws">DT Editing in Shiny application with client-side processing (server = F) throws JSON Error</a> however this isn't actually answered and provides an answer of &quot;it just does not work&quot;.</p>
<h3>kubernetes.yaml (only look if you are a wizard)</h3>
<p>I am including the yaml file incase there are some kubernetes boffins that know how to specifically solve the above issue with some clever trickery. The described problem might stem from sessions being swapped between replicas thus the data gets miscommunicated but I am honestly not the best at kubernetes... If that is the case and I would then be able to use server=TRUE within the shiny application then this would also solve the problem.</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-appname
spec:
  replicas: 5
  selector:
    matchLabels:
      app: appname
  template:
    metadata:
      labels:
        app: appname
    spec:
      containers:
      - name: appname 
        securityContext:
            privileged: false
        image: appname:latest
        ports: 
        - name: http
          containerPort: 3838
---
apiVersion: v1
kind: Service
metadata:
  name: servive-appname
spec:
  ports:
  - name: http
    port: 3838
    protocol: TCP
    targetPort: 3838
  selector:
    app: appname
  type: LoadBalancer
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-appname
  annotations:
    nginx.org/websocket-services: &quot;service-appname&quot;
spec:
  tls:
  - hosts:
    - appname.url.com
  rules:
  - host: appname.url.com
    http:
      paths:
      - path: /
        backend:
          serviceName: service-appname
          servicePort: 3838
</code></pre>

TJGorrie
<p>Here is a client-side approach, building up on @jpdugo17's answer and @TJGorrie's initial example, using the <code>stateSave</code> option to maintain the table state on re-rendering. <code>selectPage</code> and <code>updateSearch</code> can be used along with <code>dataTableProxy</code> - the state of <code>input$table_state$order</code> needs to be passed as an option:</p>
<pre><code>library(shiny)
library(DT)
data(iris)

iris$new_col &lt;- ''

server &lt;- function(input, output, session) {
  
  DF = reactiveValues(iris = iris)
  
  output$table &lt;- DT::renderDataTable(expr = {
    if (is.null(isolate(input$table_state))) {
      DT::datatable(
        DF$iris,
        selection = 'single',
        callback = JS(&quot;$.fn.dataTable.ext.errMode = 'none';&quot;),
        options = list(stateSave = TRUE)
      )
    } else {
      # print(isolate(input$table_state$order))
      DT::datatable(
        DF$iris,
        selection = 'single',
        callback = JS(&quot;$.fn.dataTable.ext.errMode = 'none';&quot;),
        options = list(
          stateSave = TRUE,
          order = isolate(input$table_state$order),
          paging = TRUE,
          pageLength = isolate(input$table_state$length)
        )
      )
    }
  }, server = FALSE)
  
  proxy &lt;- dataTableProxy('table')
  
  observeEvent(input$button, {
    DF$iris[input$table_rows_selected, c('new_col')] &lt;- 'changed!'
  })

  observeEvent(DF$iris, {
    updateSearch(proxy, keywords = list(global = input$table_state$search$search, columns = NULL)) # see input$table_state$columns if needed
    selectPage(proxy, page = input$table_state$start/input$table_state$length+1)
  }, ignoreInit = TRUE, priority = -1)
}

ui &lt;- fluidPage(
  actionButton('button', 'Press Me'),
  DT::DTOutput('table') 
)

shinyApp(ui, server)
</code></pre>
<p>Here is a <a href="https://rstudio.github.io/DT/shiny.html" rel="nofollow noreferrer">related article</a>.</p>
