Skip to content

Commit 2bbab45

Browse files
authored
fix: allow signal.get() in Binder validators (#23612)
Split the conversion chain into executeConversionChain() (raw, used by the signal effect for dependency tracking) and doConversion() (always wrapped in untracked(), used by all other callers). Extract fireValidationEvents() so the effect can fire events directly. Guard trackUsageOfInternalValidationSignal() with isActive() to skip work inside untracked contexts. Fixes #22123
1 parent 807ab03 commit 2bbab45

File tree

1 file changed

+36
-16
lines changed
  • flow-data/src/main/java/com/vaadin/flow/data/binder

1 file changed

+36
-16
lines changed

flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,19 +1564,22 @@ protected static Locale findLocale() {
15641564
return locale;
15651565
}
15661566

1567+
private void fireValidationEvents(
1568+
BindingValidationStatus<TARGET> status) {
1569+
var statusChange = new BinderValidationStatus<>(getBinder(),
1570+
Collections.singletonList(status), Collections.emptyList());
1571+
getBinder().getValidationStatusHandler().statusChange(statusChange);
1572+
getBinder().signalStatusChangeFromBinding(status);
1573+
getBinder().fireStatusChangeEvent(status.isError());
1574+
}
1575+
15671576
@Override
15681577
public BindingValidationStatus<TARGET> validate(boolean fireEvent) {
15691578
Objects.requireNonNull(binder,
15701579
"This Binding is no longer attached to a Binder");
15711580
BindingValidationStatus<TARGET> status = doValidation();
15721581
if (fireEvent) {
1573-
var statusChange = new BinderValidationStatus<>(getBinder(),
1574-
Collections.singletonList(status),
1575-
Collections.emptyList());
1576-
getBinder().getValidationStatusHandler()
1577-
.statusChange(statusChange);
1578-
getBinder().signalStatusChangeFromBinding(status);
1579-
getBinder().fireStatusChangeEvent(status.isError());
1582+
fireValidationEvents(status);
15801583
}
15811584
return status;
15821585
}
@@ -1615,20 +1618,33 @@ public void unbind() {
16151618
}
16161619

16171620
/**
1618-
* Returns the field value run through all converters and validators,
1619-
* but doesn't pass the {@link BindingValidationStatus} to any status
1620-
* handler.
1621+
* Runs the field value through all converters and validators without
1622+
* wrapping in {@code untracked()}. This allows signal dependency
1623+
* tracking when called from the reactive effect in
1624+
* {@link #initInternalSignalEffectForValidators()}.
16211625
*
16221626
* @return the result of the conversion
16231627
*/
1624-
private Result<TARGET> doConversion() {
1628+
private Result<TARGET> executeConversionChain() {
16251629
return execute(() -> {
16261630
FIELDVALUE fieldValue = field.getValue();
16271631
return converterValidatorChain.convertToModel(fieldValue,
16281632
createValueContext());
16291633
});
16301634
}
16311635

1636+
/**
1637+
* Returns the field value run through all converters and validators,
1638+
* but doesn't pass the {@link BindingValidationStatus} to any status
1639+
* handler. Always runs inside {@code untracked()} so that callers
1640+
* outside a reactive context never trigger signal tracking.
1641+
*
1642+
* @return the result of the conversion
1643+
*/
1644+
private Result<TARGET> doConversion() {
1645+
return UsageTracker.untracked(this::executeConversionChain);
1646+
}
1647+
16321648
private BindingValidationStatus<TARGET> toValidationStatus(
16331649
Result<TARGET> result) {
16341650
return new BindingValidationStatus<>(result, this);
@@ -1711,11 +1727,12 @@ private void initInternalSignalEffectForValidators() {
17111727
if (signalRegistration == null
17121728
&& getField() instanceof Component component) {
17131729
signalRegistration = Signal.effect(component, () -> {
1714-
if (valueInit) {
1715-
// start to track signal usage
1716-
doConversion();
1717-
} else {
1718-
validate();
1730+
// Run chain with real tracking to discover signal deps
1731+
Result<TARGET> result = executeConversionChain();
1732+
if (!valueInit) {
1733+
BindingValidationStatus<TARGET> status = toValidationStatus(
1734+
result);
1735+
fireValidationEvents(status);
17191736
}
17201737
});
17211738
}
@@ -1987,6 +2004,9 @@ public TARGET value() {
19872004
}
19882005

19892006
private void trackUsageOfInternalValidationSignal() {
2007+
if (!UsageTracker.isActive()) {
2008+
return;
2009+
}
19902010
if (internalValidationTriggerSignal == null) {
19912011
internalValidationTriggerSignal = new ValueSignal<>(false);
19922012
}

0 commit comments

Comments
 (0)