Signals Creating and managing reactive state primitives (Signals) activity Guide
Categories

Signals

Signals are the core primitive for reactive state in Semantic UI. They hold a value and automatically track dependencies, notifying dependent computations—Reactions—when their value changes.

Creating Signals

To create a signal import Signal from the @semantic-ui/reactivity package then initialize it with a value.

import { Signal } from '@semantic-ui/reactivity';
// Create signals for different data types
const counter = new Signal(0);
const userName = new Signal('Alice');
const isActive = new Signal(true);
const items = new Signal(['apple', 'banana']);
const userProfile = new Signal({ id: 1, name: 'Bob' });

Triggering Reactivity

A Signal’s value can be accessed using .get() or .value.

Accessing Reactive Values
console.log(counter.get()); // Output: 0
console.log(userName.value); // Output: Alice

If this occurs within a reactive context (like a Reaction or a template expression) it will establish a reactive dependency and rerun the function whenever the value is updated.

Triggering Reactivity
reaction(() => {
console.log(counter.get()); // outputs log whenever value changes
});

Controlling Reactivity There are many ways to control the reactivity of signals in your app. For instance to access a Signal’s value without creating a dependency use the .peek() method. See Reactive Performance and Reactive Controls for more information.

Updating Signal Values

Update a Signal’s value using the .set() method or by directly assigning to the .value property. This will notify any dependent Reactions to re-run.

// Using .set()
counter.set(1);
console.log(counter.get()); // Output: 1
// Using .value assignment
userName.value = 'Charlie';
console.log(userName.value); // Output: Charlie

Semantic UI also provides convenient mutation helpers directly on Signal instances for common update patterns, especially for arrays and objects.

// Using a mutation helper
counter.increment(); // Equivalent to counter.set(counter.peek() + 1)
console.log(counter.get()); // Output: 2

Signals and Foreign References

By default, Signals deep-freeze object and array values on set(). This catches the most common reactivity bug — mutating a value in place without notifying subscribers — by throwing a TypeError at the mutation site instead of silently dropping the update.

Deep-freezing has one important caveat: if the object you store is also held internally by a library, freezing it will break that library the next time it mutates its own reference.

When you need { safety: 'reference' }: if you’re storing an object in a signal that you did not construct yourself — anything returned from a library, fetched from an API, or passed through a callback — default to safety: 'reference'. Freeze is the right default for state your own code owns end-to-end. For borrowed data, reference avoids poisoning the lender’s internal references.

Worked Example: Search Index

Pagefind returns result objects and continues to use them internally — subsequent .data() calls on each result mutate pagefind’s own cached state. Storing the results under the default freeze mode freezes pagefind’s internal objects and later crashes its loader:

const defaultState = {
// ❌ default freeze — pagefind's internal mutation of the stored objects will throw
rawResults: [],
};

Opt this specific signal out of freeze:

const defaultState = {
// ✓ signal holds third-party-owned data; don't freeze
rawResults: { value: [], options: { safety: 'reference' } },
};

The rest of your component state keeps the default freeze protection — only the signal carrying borrowed data opts out.

Ad-hoc Construction

For signals created outside a defaultState declaration, pass the preset as the second argument:

const results = new Signal(pagefindData, { safety: 'reference' });

Creating Derived Signals

Signals can be transformed and combined to create new reactive values:

const items = new Signal(['apple', 'banana', 'cherry']);
// Transform a single signal with derive()
const itemCount = items.derive(arr => arr.length);
// Combine multiple signals with computed()
const shoppingList = Signal.computed(() =>
`You have ${itemCount.get()} items: ${items.get().join(', ')}`
);
console.log(shoppingList.get()); // "You have 3 items: apple, banana, cherry"

Learn More For complete information about derived and computed signals, see the Dependent Signals guide.

Advanced Usage For more configuration options, such as customizing equality checks or value cloning behavior, see the Signal Options & Configuration guide.

Previous
Reactivity
Next
Dependent Signals