Open timelyportfolio opened 4 years ago
This is genius: VueReactivity.reactive(Shiny.shinyapp.$inputValues)
, I did not know this was possible and indeed, it does not seem to break anything. I have not tested but perhaps it breaks namespaces (shiny::ns()
)?
Perhaps somewhat interestingly I was thinking along the same lines but rather differently; the idea of making shiny inputs reactive had not occurred to me. I was just exploring possibilities in private repo to essentially mimic reactiveValues
JavaScript-side. It's still too early to share as there's not much functional yet but will send that your way when done.
1) I might be completely wrong about this but sharing data from R server to JS/front-end can be made more efficient: two htmlwidgets using the cars
dataset are serialised and stored twice.
2) These are then not reactive. Ideally I would have the cars
dataset as stored JavaScript object and a proxy of it in Shiny so I can dynamically interact with it and simply have it referenced for use in htmlwidgets and elsewhere. So I can very easily change that reactive (e.g.: add/remove rows) and see the changes reflected in all the htmlwidgets/shiny inputs that make use of that dataset.
I'm not sure I make complete sense.
I invited you to the repo in question (jsdata). I genuinely do not know if what it aims to achieve is even a good idea to begin with, feel free to tell me (and be honest about it too).
library(htmltools)
library(shiny)
library(jsdata)
random_string <- function(){
paste0(sample(letters, 10), collapse = "")
}
string <- as_jsdata(random_string(), id = "string")
ui <- fluidPage(
tags$head(
tags$script(src = "https://unpkg.com/@vue/reactivity@3.0.0-rc.5/dist/reactivity.global.js"),
),
useJsdata(),
includeDataset(string),
tags$script(
HTML("datasets._datasets = datasets._datasets.map(set => VueReactivity.reactive(set));",
"$(document).on('shiny:connected', function() {",
"VueReactivity.effect(() => {
document.getElementById('test').innerText = datasets.getDataset('string')
})",
"});")
),
h1(id = "test")
)
server <- function(input, output){
observe({
invalidateLater(2000)
new_string <- as_jsdata(random_string(), id = "string")
update_dataset(new_string)
})
}
shinyApp(ui, server)
My thinking was that this might make it somewhat easier to make existing htmlwidgets support reactivity.
(If you think this approach could work I can rename and move the package to this org)
@JohnCoene I really like the idea of jsdata
but I think it likely operates best as a standalone. I don't know if you would want jsdata
locked into vue-next
reactivity. mobx
would be another very good solution. I wonder if jsdata
should provide a non-reactive foundation and then extensions could be built to supply the reactivity layer. Both mobx
and vue reactivity
are set for a new release soon.
One example @frissanalytics and I discussed was using vuex
to manage data state in JavaScript with R doing the data manipulation.
@JohnCoene https://github.com/vue-r/vueR/issues/4 and https://gist.github.com/timelyportfolio/edd70a7e40c54442aaccd5f529427fdc potentially related for jsdata
purposes.
After more thought, experimentation, and testing, I actually think valtio
might be better than all mentioned above. I made a standalone build at valtio_standalone
for easier testing without a modern (also complicated) JavaScript build toolchain.
# experiment with Shiny inputValues and valtio
# reference:
# https://github.com/pmndrs/valtio
ui <- tagList(
tags$head(
tags$script(src = "valtio.js"),
),
tags$div(
tags$h3("Increment with JavaScript"),
tags$span("Shiny: "),
textOutput("reporterR", inline = TRUE),
tags$span("JavaScript: "),
tags$span(
id = "reporterJS"
),
tags$span("valtio computed: "),
tags$span(
id = "reporterComputed"
)
),
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 = valtio.proxy(Shiny.shinyapp.$inputValues)
// do our counter using Shiny.setInputValue from JavaScript
Shiny.setInputValue('x', 0) // initialize with 0
// test valtio computed
valtio.addComputed(Shiny.shinyapp.$inputValues, {
doubled: snap => snap.x * 2,
})
valtio.subscribeKey(Shiny.shinyapp.$inputValues, 'x', (v) => {
console.log('javascript', v)
document.getElementById('reporterJS').innerText = v
})
valtio.subscribeKey(Shiny.shinyapp.$inputValues, 'doubled', (v) => {
console.log('javascript', v)
document.getElementById('reporterComputed').innerText = v
})
setInterval(
function() {
Shiny.setInputValue('x', Shiny.shinyapp.$inputValues.x + 1) //increment by 1
},
1000
)
// react to counter implemented in Shiny
valtio.subscribeKey(Shiny.shinyapp.$inputValues, 'x2:shiny.number', (v) => {
console.log('shiny', v)
document.getElementById('reporterJS2').innerText = v
})
})
"
))
)
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)
)
I have always wondered with both
vue
ormobx
+Shiny
how our workflows/architecture might change if Shiny JavaScript state inShiny.shinyapp.$inputValues
was reactive instead of a plain object. In earlier versions of JavaScript withoutproxy
, this idea is very limited in potential usage since added and deleted object properties are not tracked. However, withproxy
and the newest versions ofmobx
andvue
, we can track added or effectively replaceShiny.shinyapp.$inputValues
with a reactive version early in the session llife and reap the full benefits of JavaScript reactivity fairly cleanly.Questions
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.Code