Xstate: assign context of parent (e.g. assignParent())

Created on 28 Jan 2019  路  5Comments  路  Source: davidkpiano/xstate

Bug or feature request?

Feature

Description:

Convenience helper to assign context on a parent

(Feature) Potential implementation:

It seems like it may be a common issue to lift a child context up to the level of its parent. The only way to do that currently, afaik, is for the child to call sendParent() and then for the parent to handle that event with an assign() action.

Maybe this could be made simpler?

One approach could be a new action handler like send/assign/etc. called assignParent() that operates just like assign() except that it gets both the parent and child contexts, and the object will have updaters for the parent's context (not the child)

This does introduce some potential problems since the parent could then have its context changed unknowingly when invoking third-party machines, so another approach could be very similar to the current data capability (especially when #327 is implemented), but instead it works in reverse. Not sure what to call it - but as a strawman, myContext - and then it would follow the same idea as above - e.g. it could look like:

invoke: {
  src: "childMachine",
  myContext: {
    // will lift it up so parent.context.foo == child.context.foo
    foo: (parentContext, childContext) => childContext.foo 
  }
}
invalid

All 5 comments

I much prefer the second approach since it really should be the responsibility of the parent to decide how its context gets changed

Ever try telling your parents what to do when you were a child? Didn't ever go over too well, huh? 馃槈

This makes some assumptions that aren't necessarily true:

  • Parent context can be easily read (it can't, especially if parent-child state machines are remote, on separate threads, or context is local and private)
  • Parent context can be externally changed at any time (this violates the single responsibility principle, as well as the Actor model)

How dangerous would it be if I had a parent trafficLightMachine that invoked a child that could change the parent's context at any time?

In the Actor model, an "actor can maintain a private state that can never be changed directly by another actor" (https://www.brianstorti.com/the-actor-model/). There is very good reason for this, especially around predictability, concurrency, and determinism. The other important reason that context (private state) can't just be externally changed is that there is logic around _if_ that state can be changed, and when it can be changed, that is internal to the Actor (the machine, in this case).

So, as easy and "developer-friendly" (only short-term, not long-term) as it would be for me to create an assignParent action, it goes against the Actor model and has no basis in SCXML (remember: we have to maintain full compatibility!), and your original intuition about sending events to the parent is correct. This is how "external context changes" work in real-life as well: you request something changed, and the other Actor determines if and when its own context will be changed.

Gotcha, so sendParent+assign it is! That's the _only_ way, right?

Well, don't sendParent(assign(...)), because assign(...) is not an event.

How would you do this with a 3rd-party REST API? You'd probably make a POST or PUT request to a resource, and then it's up to the API to actually change it, if it wants to.

So, you might do something like...

on: {
  COMMIT: {
    actions: sendParent({ type: 'UPDATE_ENTITY', ... })
  }
}

yes - I meant sendParent() on the child and assign() on the parent, both as action handlers in response to their particular events

Was this page helpful?
0 / 5 - 0 ratings