Skip to content

data sharing across components #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ElianHugh opened this issue Dec 8, 2022 · 2 comments
Open

data sharing across components #12

ElianHugh opened this issue Dec 8, 2022 · 2 comments

Comments

@ElianHugh
Copy link
Owner

ElianHugh commented Dec 8, 2022

component data is intentionally isolated from other components so as to encourage comprehensible component hierarchies. however, there are situations where accessing component data externally (and non-hierarchically) is a requirement.

Possible solutions

Hierarchical reactive data

One method of hierarchical and reactive data sharing is via defining reactive values on a top-level component and passing those along to subsequent child components.

E.g.

y <- component() {
	data = function() {
		list(
			a = NULL
		)
	}
}

x <- component(
	data = function() {
		list(
			rctv = shiny::reactiveVal()
		)
	}, template = function(ns) {
		y(data = list(a = self$rctv))
	}
)

Barring the eventuality of a long chain of data passing, this has issues with understanding how data may mutate

Event bus

Like how vue does it -- basically just an event emitter

x <- event_bus()
x$emit()

Isolated reactives

Wrapper class around shiny::reactiveVal and/or shiny::reactiveValues that isolates value access from the children of components with data. E.g.

x <- component(
	data = function() {
		list(
			rctv = rsx::reactive_value()
		)
	}, template = function(ns) {
		y(data = list(a = self$rctv))
	}
)

y <- component() {
	data = function() {
		list(
			a = NULL
		)
	}, methods = list(
		setup = function(input, output, session) {
			print(self$a)              # legal usage
			self$a <- self$a + 1L # illegal usage
		}
	)
}

stateful object

Object that contains reactive values that can be accessed in any component, similar to vuex

my_store <- shiny::reactiveValues(
	a = 1,
	b = 2,
	c = 3
)

x <- component(
	data = function() {
		a = my_store$a,
		b = my_store$b,
		c = my_store$c
	}
)

callback (child to parent)

x <- component(
	data = function() {
		list(
			a = 1L,
			update_a = function() {
				self$a <- self$a + 1L
			}
		)
	},
	template = function(ns) {
		y(data = list(cb = update_a))
	}
)

y <- component() {
	data = function() {
		list(
			cb = NULL
		)
	}, methods = list(
		setup = function(input, output, session) {
			self$cb()
		}
	)
}
@ElianHugh
Copy link
Owner Author

ElianHugh commented Dec 10, 2022

Leaning toward the idea of isolated reactives, but this would ideally need a nice interface. Some ideas:

Wrapper

data = function() {
  list(
        x = reactive_value()
  )
}

Issues with this approach:

  • unclear when a variable is an isolated reactive without specifically checking
  • confusing to have shiny::reactiveVal and rsx::reactive_value

New component property

props = function() {
  list()
}

This creates a nice flow of props to data when passing values. E.g.

x <- component(
	state = function() {
		list(
			rctv = 1L
		)
	}, template = function(ns) {
		y(data = list(a = self$rctv))
	}
)

y <- component() {
	data = function() {
		list(
			a = NULL
		)
	}
}

Also ensures that the state cannot be modified outside the component (because only data can be passed between components)

Issues:

  • unclear what the naming scheme would be
    • props?
    • reactives?
    • state?

@ElianHugh
Copy link
Owner Author

ElianHugh commented Dec 11, 2022

New component property: state

Steps to implementation:

  1. Add state arguments to component generator functions

    component <- function(name = paste0("unnamed_component-", random_id()),

    rsx/R/component.R

    Lines 20 to 27 in 68cca16

    this <- new_component(
    name = name,
    data,
    methods,
    template,
    styles,
    parent = parent.frame()
    )

    new_component <- function(name, data, methods, template, styles, parent) {

  2. Add state attribute to component generator function

rsx/R/component.R

Lines 37 to 49 in 68cca16

structure(
function(...) {
instantiate(sys.function())(...)
},
class = "component",
component_id = component_id,
name = name,
data = create_data_property(data),
methods = methods,
template = create_template_property(template),
styles = create_styles_property(styles, component_id),
.parent = parent
)

  1. Add state generator validator (check is function)

validate_component <- function(self) { #nolint

  1. Create wrapper class for state values (may have to re-implement shiny accessor methods)

  2. Generate state values inside instance via instantiator function

    rsx/R/instance.R

    Lines 34 to 41 in 68cca16

    new_instance <- function(comp) {
    this <- create_env_bindings(comp)
    inst_methods <- instantiate_functions(attr(comp, "methods"), this)
    inst_data <- instantiate_data(attr(comp, "data"), this)
    inst_data_nms <- names(inst_data)
    inst_method_nms <- names(inst_methods)

  3. Add state values (reactiveVals) to instance self environment

    list2env(c(inst_data, inst_methods), this$internal$self)

  4. Create active binding "state" alongside "data" and "method" bindings in instance

    rsx/R/instance.R

    Lines 72 to 91 in 68cca16

    new_active_binding(
    "data",
    getter = function() {
    get_nonfunctions(this$internal$self)
    },
    setter = function(v) {
    error_instance_assignment(this, "data")
    },
    env = this
    )
    new_active_binding(
    "methods",
    getter = function() {
    get_functions(this$internal$self)
    },
    setter = function(v) {
    error_instance_assignment(this, "methods")
    },
    env = this
    )

  5. Add testthat tests for state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant