flow icon indicating copy to clipboard operation
flow copied to clipboard

Ability to modify state in Signal.effect

Open abdullahtellioglu opened this issue 7 months ago • 4 comments

Describe your motivation

In some cases, when you receive a signal effect, you want to update another value based on the new value. But Signal does not allow you to do it, as it throws Cannot make changes in a read-only transaction.

Describe the solution you'd like

I understand that when you allow that, the code would get stuck in an infinite loop, but there should be a way. Maybe Signal.invokeSilently(), or something like that.

Describe alternatives you've considered

Another solution would be Signal.effect would get ValueSignal parameter(s), where signal.effectgets invoked only when changes happen on those values.

Additional context

If nothing is provided by Signal API, at least more informative exception can be thrown and should explain how to overcome this issue and what user should do.

abdullahtellioglu avatar Jun 03 '25 07:06 abdullahtellioglu

How about using Signal.computed(computation)? This at least should solve the "you want to update another value based on the new value" case.

mshabarov avatar Jun 03 '25 10:06 mshabarov

The need for this is typically a symptom of not having a clear model of what is the actual state and what is derived from it. Could you give a concrete example of what you're trying to achieve?

With that being said, a workaround does exist based on the fact that the limitation is implemented by running the callback in a readonly transaction: Signal.runWithoutTransaction(() -> otherSignal.value(foo));

Legioth avatar Jun 05 '25 07:06 Legioth

I created a game where users can make at most three mistakes. What I tried initially was to listen to changes on the Game object and try to update another signal -something called GameOver boolean value- when user reached the mistake limit. But I could not do it as it is not allowed to update a Signal value in Signal.effect.

I was not aware of the runWithoutTransaction(). The use case above might cause an infinite loop event with runWithoutTransaction as it will trigger Signal.effect. It would work if you could explicitly set which signal value this code reacts to

abdullahtellioglu avatar Jun 05 '25 08:06 abdullahtellioglu

That sounds like a perfect case for a computed signal, e.g. something like this:

Signal<Boolean> gameOver = Signal.computed(() -> {
  return gameStateSignal.value().mistakes() >= 3;
});

Legioth avatar Jun 05 '25 09:06 Legioth

This was already addressed in https://github.com/vaadin/flow/pull/22215

Legioth avatar Nov 03 '25 09:11 Legioth