#Can I mutate properties in a context?

1 messages · Page 1 of 1 (latest)

buoyant fractal
#

I know I should use assign to a new value to a property in a context.
What I want to do is not assining, but mutating the value of property.
For example, I need Map object in a context and entries should be added/deleted by events. I don't need to mutate context object itself, but mutate its property.
To avoid this, I should use an immutable map instead or create a new Map object from entries at each update. Neither this way is satisfying.
What is the recommended way to solve this problem?

narrow flicker
buoyant fractal
#

Immer is helpful to do updates immutable objects, but I want to avoid doing so.

buoyant fractal
polar orbit
#

You can mutate anything you like but if you step outside the assign model of updating context, you open yourself up for potential problems later. I like to use the Map type from fp-ts to handle this type of thing, makes it very easy to get a new Map as the result of updating a property which works great with assign

dark turtle
#

You should not mutate properties in context

buoyant fractal
#

Then should I use an immutable map object instead? I'm using xstate + react and that mutable map is not directly used in rendering, and I don't see which problem can occur.

dark turtle
#

You can either use immer or do immutable techniques like { ...spreading } the object

buoyant fractal
#

Yes. I'm doing so. However I don't understand why it is forbidden to use internal(not used by the actor's subscribers) mutable values in context yet.

dark turtle
#

Can you share an example of what you're doing?

buoyant fractal
#

First, I'm using React Native and Reanimated, one of its popular library, has a mutable state for animation named 'shared value'.
I'd like to xstate machine to manage the animations as actions, so shared values need to be stored in context and updated.
Its usage is like the usage of opaque handle, but its value is actully being mutated via animation functions.

Second, I want to store a key-value map for some objects inside context. I need to check the existence of a key efficiently and update the value of a key.
The map is not subscribed from outside, which means the referential transparency of getSnapshot seems not that important.
I can do this by object instead of Map and utilize speard operator or immer as you mentioned. In the current situation, the number of objects is not so large, so I think it's okay to use object.
However, I'm worried about the performance of the spread operator or immer when the number of objects becomes large.
And if it's not allowed to use mutable object in context even in the case of large number of objects, then I'm not sure it's a good idea to use xstate with such limitation.

dark turtle
#

Example of idempotency:

  • assign({ items: ({ context }) => { context.items.push(...); return context.items }) })
  • assign({ set: ({ context }) => { context.set.add(...); return context.set } })
buoyant fractal
#

Could you explain which part of the exmaple is related to idempotency please?

polar orbit
#

Idempotency means that f(x) produces the same result as f(f(x)). So set.add is idempotent because if you call it repeatedly with the same value, the set will only ever be 1 larger while with array.push, multiple calls will continually extend the length of the array.

buoyant fractal
#

In that case, the idempotency dependes on which value is added/pushed.

#

I don't understand the premise that the same value will be added to the set.

#

Where did the premise come frome?

polar orbit
#

Set is a data structure that can't contain duplicate values (or references for objects) so when you call set.add(12) multiple times, the set will only ever include a single entry that is 12

In contrast, you will have the same number of new entries of 12 in a given array as the number of times arr.push(12) is called on it.

Idempotency is about whether repeating an identical operation on a data structure produces the same state, the parameters you call a function with are included as part of the definition of the operation so if you change the parameters of an operation, it no longer counts as the same operation for the purpose of determining idempotency

buoyant fractal
#

I know idempotency well enough. But yes, the same value premise thing I said was wrong.

What confuses me is how it is related to the usage in context.
Do you mean that actions are expected to be idempotent, which means multiple exeuctions of a action with the same events should make the same result with a single execution? I believe you don't.
If not, what exactly should be idempotent? I don't find the what f and x is in the context of the assign function. Could you explain in more detail please?

polar orbit
#

If you use mutable objects in your actions, you should use idempotent operations. If you aren't mutating anything, you don't need to worry about it.

#

With regard to the assign function in the examples David gave, f would be set.add and x would be the items being added to the set so f(f(x)) would be the same as f(x)

#

then in the example of what not to do f was items.push so f(f(x)) is not the same as f(x)

polar orbit
#

Looking at Reanimated, I'm not sure it makes sense to try and embed its value state management into Xstate's context, both useSharedValue and context in Xstate are solving the problem of updating listeners when values change

#

I would give it a go at a vertical slice anyway because at least then you'll have a better idea of where the overlap is and which is best to lean on

#

Depends on what you're actually doing but it might make the most sense to rely on Reanimated for the values and then use Xstate actions to execute updates to those values as an external side effect rather than an internal update of context

buoyant fractal
#

I think I'm misunderstanding something important, and the Y/N answer to this question will help me follow your explanation.
Do you mean that actions are expected to be idempotent?

polar orbit
#

No

polar orbit
buoyant fractal
#

if x is allowed to be a value derived from context, it seems that non-idempotency can be introduced even with Set whose every operations are idempotent.
For example,

assign(({ context, event }) => ({
  set: context.set.add(context.inc)
  inc: context.inc + 1
}))

I agree that this example is not natural, but I think it's possible to write such a code.

#

Ah... in this case, context's value is different yes

#

Now I am at the new stage.
I thought that the idempotency rule is hard-to-follow/why-it-is-necessary.
And now, I think that the idempotency rule is ok-to-follow/but still why-it-is-necessary.

polar orbit
#

To go any deeper on why it it's the case we'd need someone more familiar with the internals that lead to the advice David gave

buoyant fractal