Open
Description
I have always wondered with both vue
or mobx
+ Shiny
how our workflows/architecture might change if Shiny JavaScript state in Shiny.shinyapp.$inputValues
was reactive instead of a plain object. In earlier versions of JavaScript without proxy
, this idea is very limited in potential usage since added and deleted object properties are not tracked. However, with proxy
and the newest versions of mobx
and vue
, we can track added or effectively replace Shiny.shinyapp.$inputValues
with a reactive version early in the session llife and reap the full benefits of JavaScript reactivity fairly cleanly.
Questions
- I have not tested real-life usage with large complex apps, but in theory it seems there is no impact. Am I missing something fundamental that would prove this idea is not as feasible as it seems.
- I doubt
Shiny
proper would ever pursue a reactiveJS
state since it would have to choose which reactive state engine it would use. Is there potential forShiny
to make the choice based on active community and well-tested, stable JS dependencies forShiny
to have a reactive JS engine as its input state? Which library would most likely meet the requirements? I would think choosing an existing library would be better than developing one from scratch. - I feel like the concept of reactivity is quickly engrained in most Shiny developers and the reactivity concepts would translate easily to JavaScript. What would get in the way of quick understanding for R Shiny developers? What might be intimidating or difficult?
- How might we best communicate the improvement from incorporating reactive JS input state?
- What tools/tooling could we provide to ease the integration?
Code
library(htmltools)
library(vueR)
library(shiny)
# experiment with standalone vue reactivity in bare page
# reference:
# https://vuejs.org/v2/guide/reactivity.html
# https://dev.to/jinjiang/understanding-reactivity-in-vue-3-0-1jni
browsable(
tagList(
tags$head(
tags$script(src = "https://unpkg.com/@vue/[email protected]/dist/reactivity.global.js"),
),
tags$p("we should see a number starting at 0 and increasing by one each second"),
tags$div(id = "reporter"),
tags$script(HTML(
"
let data = {x: 0};
let data_reactive = VueReactivity.reactive(data) // could also use ref for primitive value
console.log(data, data_reactive)
VueReactivity.effect(() => {
console.log(data_reactive.x)
document.getElementById('reporter').innerText = data_reactive.x
})
setInterval(function() {data_reactive.x++}, 1000)
"
))
)
)
# experiment with Shiny inputValues and vue-next
# reference:
# https://vuejs.org/v2/guide/reactivity.html
# https://dev.to/jinjiang/understanding-reactivity-in-vue-3-0-1jni
ui <- tagList(
tags$head(
tags$script(src = "https://unpkg.com/@vue/[email protected]/dist/reactivity.global.js"),
),
tags$div(
tags$h3("Increment with JavaScript"),
tags$span("Shiny: "),
textOutput("reporterR", inline = TRUE),
tags$span("JavaScript: "),
tags$span(
id = "reporterJS"
)
),
tags$div(
tags$h3("Increment with R/Shiny"),
tags$span("Shiny (used numeric input for convenience): "),
numericInput(inputId = 'x2', label = "", value = 0),
tags$span("JavaScript: "),
tags$span(
id = "reporterJS2"
)
),
tags$script(HTML(
"
$(document).on('shiny:connected', function() {
// once Shiny connected replace Shiny inputValues with reactive Shiny inputValues
Shiny.shinyapp.$inputValues = VueReactivity.reactive(Shiny.shinyapp.$inputValues)
// do our counter using Shiny.setInputValue from JavaScript
Shiny.setInputValue('x', 0) // initialize with 0
VueReactivity.effect(() => {
console.log('javascript', Shiny.shinyapp.$inputValues.x)
document.getElementById('reporterJS').innerText = Shiny.shinyapp.$inputValues.x
})
setInterval(
function() {
Shiny.setInputValue('x', Shiny.shinyapp.$inputValues.x + 1) //increment by 1
},
1000
)
// react to counter implemented in Shiny
VueReactivity.effect(() => {
console.log('shiny', Shiny.shinyapp.$inputValues['x2:shiny.number'])
document.getElementById('reporterJS2').innerText = Shiny.shinyapp.$inputValues['x2:shiny.number']
})
})
"
))
)
server <- function(input, output, session) {
x2 <- 0 # use this for state of Shiny counter
output$reporterR <- renderText({input$x})
observe({
invalidateLater(1000, session = session)
x2 <<- x2 + 1 # <<- or assign required to update parent
updateNumericInput(inputId = "x2", value = x2, session = session)
})
}
shinyApp(
ui = ui,
server = server,
options = list(launch.browser = rstudioapi::viewer)
)