Docs

Documentation versions (currently viewingVaadin 25.1 (pre-release))

Effects and Computed Signals

Using effects and computed signals for reactive UI updates.

Effects and computed signals are the core mechanisms for custom reactive logic. Effects automatically re-run when their signal dependencies change, while computed signals derive values from other signals.

Note
Preview Feature

This is a preview version of Signals. You need to enable it with the feature flag com.vaadin.experimental.flowFullstackSignals. Preview versions may lack some planned features, and breaking changes may be introduced in any Vaadin version. We encourage you to try it out and provide feedback to help us improve it.

For binding signals directly to component properties like text, visibility, and form field values, see Component Bindings. This section covers effects for custom logic and computed signals for derived values.

When to Use Effects

Use direct binding methods (covered in Component Bindings) when:

  • Binding text content with bindText()

  • Binding visibility with bindVisible()

  • Binding enabled state with bindEnabled()

  • Binding form field values with bindValue()

  • Binding styles, themes, or class lists

Use ComponentEffect when:

  • You need custom side effects beyond property binding

  • You’re working with component APIs that don’t have binding methods

  • You need to execute logic that isn’t just setting a property

Component Effects

Effects are callbacks that run immediately when created and automatically re-run when any signal value they depend on changes. Dependencies are automatically managed based on the signals that were used the last time the callback was run.

Creating Component-Bound Effects

The ComponentEffect.effect() method creates an effect bound to a component’s lifecycle. The effect is active while the component is attached and inactive while detached.

Source code
Java
Chart chart = new Chart(ChartType.AREASPLINE);
ListSeries series = new ListSeries("My Series", new Number[0]);
SharedListSignal<Number> seriesSignal = new SharedListSignal<>(Number.class);
// initialization details omitted

ComponentEffect.effect(chart, () -> {
    // This code runs immediately and again whenever seriesSignal changes
    series.setData(seriesSignal.value().stream()
        .map(Signal::value).toArray(Number[]::new));
});

The first parameter is the component that owns the effect. When the component is detached, the effect is paused. When re-attached, the effect resumes and runs again with current values.

The method returns a Registration instance that can be used to remove the effect before the component is detached:

Source code
Java
Registration effectRegistration = ComponentEffect.effect(chart, () -> {
    // effect logic
});

// Later, when you need to stop the effect
effectRegistration.remove();

Custom Effects for Non-Bindable Properties

Use effects for component properties that don’t have direct binding methods:

Source code
Java
ComponentEffect.effect(component, () -> {
    component.setCustomProperty(signal.value());
});

Computed Signals

Computed signals derive their values from other signals. They automatically update when any of their dependencies change.

Source code
Java
Signal<String> fullName = Signal.computed(() -> {
    return firstNameSignal.value() + " " + lastNameSignal.value();
});

Computed signals are read-only; you cannot set their value directly.

Combining Multiple Signals

The primary use case for computed signals is combining values from multiple source signals:

Source code
Java
ValueSignal<Double> price = new ValueSignal<>(100.0);
ValueSignal<Double> quantity = new ValueSignal<>(2.0);
ValueSignal<Double> taxRate = new ValueSignal<>(0.1);

Signal<Double> total = Signal.computed(() -> {
    double subtotal = price.value() * quantity.value();
    return subtotal * (1 + taxRate.value());
});
// total.value() returns 220.0

quantity.value(3.0);
// total.value() now returns 330.0

Caching and Lazy Evaluation

Computed signals cache their value and only recalculate when dependencies change:

Source code
Java
Signal<Integer> expensiveComputation = Signal.computed(() -> {
    // This only runs when dependencies change
    return performExpensiveCalculation(inputSignal.value());
});

// Multiple reads return the cached value
expensiveComputation.value(); // Computes once
expensiveComputation.value(); // Returns cached value

Signal Mapping

The map() method transforms a signal’s value. Use this when deriving a value from exactly one signal:

Source code
Java
SharedValueSignal<Integer> age = new SharedValueSignal<>(Integer.class);

Signal<String> ageCategory = age.map(a ->
    a < 18 ? "Child" : (a < 65 ? "Adult" : "Senior"));

Signal<Boolean> adult = age.map(a -> a >= 18);

Use map() for single-signal transformations. For transformations depending on multiple signals, use Signal.computed() instead.

Tip

The read-only map() method shown above creates a one-way transformation. For two-way binding scenarios where changes need to flow in both directions (for example, binding a checkbox to a property of a record), use map(getter, merger) or mapMutable(getter, modifier). See Two-Way Signal Mapping for details.

Negating Boolean Signals

The Signal.not() method creates a computed signal that negates a boolean signal:

Source code
Java
ValueSignal<Boolean> loading = new ValueSignal<>(true);

// Create a negated signal
Signal<Boolean> notLoading = Signal.not(loading);

Reading without Dependencies

Using peek() to Avoid Dependencies

Use peek() to read a signal’s value without registering a dependency:

Source code
Java
ComponentEffect.effect(component, () -> {
    // value() registers a dependency - effect will re-run when nameSignal changes
    String name = nameSignal.value();

    // peek() doesn't register a dependency - effect won't re-run when countSignal changes
    int count = countSignal.peek();
});

Executing Code without Tracking

The Signal.untracked() method executes a block of code without tracking any signal dependencies:

Source code
Java
ComponentEffect.effect(component, () -> {
    // This read is tracked
    String trackedValue = trackedSignal.value();

    // Everything inside untracked() is not tracked
    Signal.untracked(() -> {
        String untrackedValue = anotherSignal.value(); // Not tracked
        processValue(untrackedValue);
    });
});

This is useful when you need to read signal values for logging, analytics, or other side effects without creating dependencies.

Standalone Effects

A standalone signal effect can be used for effects that aren’t related to any UI component. The effect remains active until explicitly cleaned up.

Source code
Java
CleanupCallback cleanup = Signal.effect(() -> {
    System.out.println("Counter updated to " + counter.value());
});

// Later, when the effect is no longer needed
cleanup.cleanup();
Warning
Standalone effects can lead to memory leaks through any instances referenced by the closure of the effect callback. Always store and call the cleanup function when the effect is no longer needed.

Dependency Tracking

Dependencies are tracked dynamically based on which signals are actually read during execution:

Source code
Java
Signal<Boolean> showDetails = new SharedValueSignal<>(Boolean.class);
Signal<String> summary = new SharedValueSignal<>(String.class);
Signal<String> details = new SharedValueSignal<>(String.class);

ComponentEffect.effect(component, () -> {
    if (showDetails.value()) {
        // Both showDetails and details are dependencies
        component.setText(details.value());
    } else {
        // Only showDetails and summary are dependencies
        component.setText(summary.value());
    }
});

When showDetails is false, changes to details won’t trigger the effect because it wasn’t read in the last execution.

Best Practices

Prefer Direct Bindings Over Effects

Use component binding methods for property updates. Effects are for custom logic that bindings can’t handle:

Source code
Java
// Preferred: Direct binding
label.bindText(signal);

// Avoid: Effect for simple property binding
ComponentEffect.effect(label, () -> label.setText(signal.value()));

See Component Bindings for available binding methods.

Avoid Changing Signals in Effects

Updating a signal in reaction to another signal might cause an infinite loop. Effect and computed signal callbacks run inside a read-only transaction to prevent accidental changes.

Use a computed signal when one signal’s value depends on another:

Source code
Java
// Good: Use computed signal for derived values
Signal<String> derivedValue = Signal.computed(() -> {
    return sourceSignal.value().toUpperCase();
});

If you must modify a signal from an effect and are certain there’s no risk of infinite loops, use runWithoutTransaction():

Source code
Java
ComponentEffect.effect(component, () -> {
    String value = oneSignal.value();

    // WARNING: This might lead to infinite loops.
    // Do this only if absolutely necessary.
    Signal.runWithoutTransaction(() -> {
        otherSignal.value(value);
    });
});

Keep Effects Focused

Each effect should have a single responsibility. For multiple property updates, use separate bindings.

Using separate effects or bindings for different concerns improves efficiency: when a signal changes, only the components that depend on it are updated. For example, in a dashboard with charts and cards, separate effects ensure that a single data change triggers updates only in the specific dependent component, rather than re-rendering unrelated parts of the UI.

Source code
Java
// Good: Separate bindings for separate concerns
nameLabel.bindText(userSignal.map(User::name));
ageLabel.bindText(userSignal.map(u -> String.valueOf(u.age())));

// Avoid: One large effect doing multiple things
ComponentEffect.effect(container, () -> {
    nameLabel.setText(userSignal.value().name());
    ageLabel.setText(String.valueOf(userSignal.value().age()));
    // ... more updates
});