Internal State Reactive data storage for your component's internal behavior database Guide

Internal State

Overview

Component state represents the dynamic, internal data managed within a component, often changing in response to user interaction or other events.

Unlike settings, which act as the external configuration API, state is private to the component instance.

Semantic UI uses its Signals-based reactivity system to manage state.

For a deeper understanding of reactivity concepts, see the Reactivity Guide.

Declaring State

Default state values are provided via the defaultState object when creating a component using defineComponent.

When a component instance is initialized, each property in the defaultState object is automatically converted into a reactive Signal, allowing its value to be tracked and updated.

const defaultState = {
selectedItem: null,
items: []
};
defineComponent({
tagName: 'my-component',
defaultState
});

Accessing State

Accessing in Component Logic

The state object is passed as an argument to lifecycle callbacks (like createComponent, onRendered, etc.) and handlers for events or key bindings. You can destructure state to access individual state signals within these functions.

const createComponent = ({state}) => ({
incrementCounter() {
// Modify state with .value - triggers re-renders
state.counter.value += 1;
},
getCount() {
// Read state using .value
return state.counter.value;
// Or using .get() method
// return state.counter.get();
}
});

Flexible Syntax Both .value and .get() can be used to read a signal’s current value within component logic. See Reactivity Basics for details. Note that accessing state within a Reaction establishes a dependency.

Accessing in Templates

Within a component’s template, state signals can be accessed directly by their property name from the data context. You do not need to use .value or .get() here.

As an added convenience state values in templates can be accessed by name directly without using .value or .get()

{state.selectedItem} <!-- Wrong !-->
{selectedItem.get} <!--- Wrong !-->
{selectedItem} <!--- Right !-- >

For instance for the following state configuration

const defaultState = {
isActive: false,
items: []
};
<p>Count: {counter}</p>
{#if isActive}
<div class="active-content">
<p>Currently active!</p>
</div>
{else}
<button class="activate">Activate</button>
{/if}
{#each items}
<li class="{#if selectedItem === id}selected{/if}">
{title}
</li>
{/each}

Accessing in Event Handlers & Keybindings

As mentioned, the state object is available within declarative event handlers and key binding callbacks, allowing direct state manipulation in response to user input.

defineComponent({
// ...other component options
events: {
'click .activate'({state}) {
state.isActive.toggle();
}
}
});
defineComponent({
// ...other component options
keys: {
'up'({state}) {
state.x.decrement(10);
}
}
});

Updating State

Basic Updates & Helpers

Update a state signal’s value using its .set() method, by assigning to its .value property, or by using convenient built-in mutation helpers (like .increment(), .toggle(), .now(), etc.) where available.

const onButtonClick = ({state}) => {
state.counter.increment(1);
state.lastClicked.value = new Date(); // equivalent
state.lastClicked.set(new Date()); // equivalent
state.lastClicked.now(); // helper equivalent
};

Controlling Update Timing

Reactive updates are typically batched and flushed asynchronously. When multiple state changes need to occur together atomically, or you need to run code after the DOM has been updated based on state changes, you can use the afterFlush callback available in lifecycle methods.

const createComponent = ({$, state, afterFlush}) => ({
resetForm() {
// Make multiple state changes
state.username.value = '';
state.email.value = '';
state.isSubmitted.value = false;
state.errors.value = [];
// Run code after all updates are processed
afterFlush(() => {
console.log('Form reset complete');
// Focus the first field
$('input[name="username"]').focus();
});
}
});

Working with Collections

State signals wrapping arrays or objects provide built-in mutation helpers (like .push(), .removeIndex(), .setProperty()) that simplify common update operations while automatically handling reactivity.

See the Mutation Helpers guide and the Reactivity API Reference (specifically collection and array helpers) for more details.

const createComponent = ({state}) => ({
// Adding items to an array
addItem() {
// Using the built-in push method
state.items.push({ id: generateId(), text: 'New Item' });
},
// Removing items by index
removeIndex(index) {
state.items.removeIndex(index);
},
// Removing items by ID
removeById(id) {
state.items.removeItem(id);
},
// Updating array items
updateItem(index, newText) {
// Update item at specific index
state.items.setIndex(index, { ...state.items.getIndex(index), text: newText });
// Or update a property across all items
state.items.setArrayProperty('isActive', false);
// Or update a property on a specific item
state.items.setArrayProperty(2, 'isActive', true);
},
// Object property updates
updateProperty() {
state.user.setProperty('name', 'Alex');
}
});

These helper methods automatically handle reactivity updates and trigger re-renders as needed.

Lifecycle Integration

State management is often closely tied to the component lifecycle. Common patterns include:

  • Initializing state based on settings in createComponent or onCreated.
  • Fetching data and updating state in onCreated or onRendered.
  • Setting up reactive effects (using reaction) that respond to state changes within createComponent or onCreated.
  • Cleaning up resources (e.g., timers, subscriptions stored in state) in onDestroyed.

See the Reactive Computations guide for more information on creating reactive effects.

// Initialize state after component rendering
const onRendered = ({state, settings}) => {
// Initialize state based on settings
state.items.value = settings.initialItems || [];
// Start with first item selected if available
if (state.items.value.length > 0) {
state.selectedItem.value = state.items.value[0].id;
}
};
// Clean up state before component is removed
const onDestroyed = ({state}) => {
// Clear any timers, subscriptions, etc.
if (state.updateTimer.value) {
clearInterval(state.updateTimer.value);
}
};
Previous
Settings
Next
Events