The state package provides reactive state management utilities built on top of Preact Signals, including signals, computed values, effects, decorators, and history tracking.
Core Functions
signal
Creates a reactive signal that can be subscribed to. Re-exported from @preact/signals-core.
function signal<T>(value: T): Signal<T>
A signal object with .value property and .peek() method
Usage Example
import {signal} from '@dnd-kit/state';
const count = signal(0);
console.log(count.value); // 0
count.value = 5;
console.log(count.value); // 5
// Peek without subscribing
console.log(count.peek()); // 5
computed
Creates a computed signal that automatically updates when its dependencies change.
function computed<T>(
compute: () => T,
comparator?: (a: T, b: T) => boolean
): ReadonlySignal<T>
Function that computes the value
Optional equality comparator to prevent unnecessary updates
A readonly signal that updates when dependencies change
Usage Example
import {signal, computed} from '@dnd-kit/state';
const width = signal(100);
const height = signal(50);
const area = computed(() => width.value * height.value);
console.log(area.value); // 5000
width.value = 200;
console.log(area.value); // 10000 - automatically updated
// With custom comparator
const items = signal([1, 2, 3]);
const doubled = computed(
() => items.value.map(x => x * 2),
(a, b) => a.length === b.length && a.every((v, i) => v === b[i])
);
effect
Runs a side effect function whenever its dependencies change. Re-exported from @preact/signals-core.
function effect(fn: () => void | CleanupFunction): () => void
fn
() => void | CleanupFunction
required
Effect function that runs when dependencies change. Can optionally return a cleanup function.
A function to stop the effect and run cleanup
Usage Example
import {signal, effect} from '@dnd-kit/state';
const name = signal('Alice');
const dispose = effect(() => {
console.log(`Hello, ${name.value}!`);
// Optional cleanup
return () => {
console.log('Cleaning up...');
};
});
// Logs: "Hello, Alice!"
name.value = 'Bob';
// Logs: "Cleaning up..."
// Logs: "Hello, Bob!"
dispose();
// Logs: "Cleaning up..."
// Effect is now stopped
effects
Runs multiple effects and returns a single cleanup function.
function effects(...entries: Effect[]): CleanupFunction
Multiple effect functions to run
A function that cleans up all effects
Usage Example
import {signal, effects} from '@dnd-kit/state';
const x = signal(0);
const y = signal(0);
const cleanup = effects(
() => console.log(`x: ${x.value}`),
() => console.log(`y: ${y.value}`),
() => console.log(`sum: ${x.value + y.value}`)
);
x.value = 5;
// Logs: "x: 5"
// Logs: "sum: 5"
cleanup();
// All effects stopped
batch
Batches multiple signal updates to prevent unnecessary re-renders. Re-exported from @preact/signals-core.
function batch(fn: () => void): void
Function that performs multiple signal updates
Usage Example
import {signal, batch, computed} from '@dnd-kit/state';
const firstName = signal('John');
const lastName = signal('Doe');
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
let computeCount = 0;
effect(() => {
fullName.value;
computeCount++;
});
// Without batch: triggers 2 computations
firstName.value = 'Jane';
lastName.value = 'Smith';
// computeCount: 2
// With batch: triggers 1 computation
batch(() => {
firstName.value = 'Alice';
lastName.value = 'Johnson';
});
// computeCount: 3 (only one additional computation)
untracked
Reads a signal value without subscribing to it. Re-exported from @preact/signals-core.
function untracked<T>(fn: () => T): T
Function that reads signals
The return value of the function
Usage Example
import {signal, computed, untracked} from '@dnd-kit/state';
const a = signal(1);
const b = signal(2);
const sum = computed(() => {
// This computed will only depend on 'a'
const aValue = a.value;
const bValue = untracked(() => b.value);
return aValue + bValue;
});
console.log(sum.value); // 3
a.value = 10;
console.log(sum.value); // 12 - recomputed
b.value = 20;
console.log(sum.value); // 12 - NOT recomputed (b is untracked)
Decorators
@reactive
Decorator that makes a class accessor reactive using signals.
function reactive<This, Value>(
target: ClassAccessorDecoratorTarget<This, Value>,
context: ClassAccessorDecoratorContext<This, Value>
): ClassAccessorDecoratorResult<This, Value>
Usage Example
import {reactive} from '@dnd-kit/state';
class Counter {
@reactive
accessor count = 0;
increment() {
this.count++;
}
}
const counter = new Counter();
effect(() => {
console.log(counter.count);
});
// Logs: 0
counter.increment();
// Logs: 1
@derived
Decorator that makes a getter computed, caching its result and only recomputing when dependencies change.
function derived<This, Return>(
target: (this: This) => Return,
context: ClassGetterDecoratorContext<This, Return>
): (this: This) => Return
Usage Example
import {reactive, derived} from '@dnd-kit/state';
class Rectangle {
@reactive
accessor width = 100;
@reactive
accessor height = 50;
@derived
get area() {
return this.width * this.height;
}
}
const rect = new Rectangle();
console.log(rect.area); // 5000
rect.width = 200;
console.log(rect.area); // 10000 - automatically recomputed
@enumerable
Decorator that controls whether a property is enumerable.
function enumerable(enumerable?: boolean): PropertyDecorator
Whether the property should be enumerable
Usage Example
import {enumerable} from '@dnd-kit/state';
class Example {
public visible = 'shown';
@enumerable(false)
public hidden = 'not shown';
}
const obj = new Example();
console.log(Object.keys(obj));
// ['visible'] - 'hidden' is not enumerable
for (const key in obj) {
console.log(key); // Only 'visible'
}
Utilities
deepEqual
Performs deep equality comparison between two values.
function deepEqual<T>(a: T, b: T): boolean
True if values are deeply equal
Usage Example
import {deepEqual} from '@dnd-kit/state';
deepEqual({a: 1, b: 2}, {a: 1, b: 2}); // true
deepEqual([1, 2, 3], [1, 2, 3]); // true
deepEqual(new Set([1, 2]), new Set([1, 2])); // true
deepEqual({a: {b: 1}}, {a: {b: 2}}); // false
// Use with computed
const data = signal({items: [1, 2, 3]});
const processed = computed(
() => ({items: data.value.items.map(x => x * 2)}),
deepEqual
);
snapshot
Creates a plain object snapshot of a reactive object without triggering subscriptions.
function snapshot<T extends object>(value: T): T
The reactive object to snapshot
A plain object copy with all enumerable properties
Usage Example
import {snapshot, reactive} from '@dnd-kit/state';
class State {
@reactive
accessor x = 10;
@reactive
accessor y = 20;
}
const state = new State();
const snap = snapshot(state);
console.log(snap); // {x: 10, y: 20}
// Snapshot is a plain object
console.log(snap instanceof State); // false
Advanced Utilities
ValueHistory
A class that tracks current, previous, and initial values with reactive updates.
class ValueHistory<T> implements WithHistory<T> {
constructor(
defaultValue: T,
equals?: (a: T, b: T) => boolean
)
}
The default/initial value
equals
(a: T, b: T) => boolean
default:"Object.is"
Equality comparator for detecting changes
Properties
The current value (reactive)
The previous value, or undefined if not yet changed (reactive)
The initial value (reactive)
Methods
reset
Resets to the default value or a new value.
Usage Example
import {ValueHistory} from '@dnd-kit/state';
const history = new ValueHistory(0);
console.log(history.current); // 0
console.log(history.previous); // undefined
history.current = 5;
console.log(history.current); // 5
console.log(history.previous); // 0
history.current = 10;
console.log(history.current); // 10
console.log(history.previous); // 5
console.log(history.initial); // 0
history.reset();
console.log(history.current); // 0
console.log(history.previous); // undefined
WeakStore
A weak reference store that associates values with object keys and identifiers.
class WeakStore<
WeakKey extends object,
Key extends string | number | symbol,
Value extends Record<Key, any>
>
Methods
get
Retrieves a value from the store.
get(key: WeakKey | undefined, id: Key): Value | undefined
key
WeakKey | undefined
required
The weak key (object reference)
The identifier within that key’s map
The stored value, or undefined if not found
set
Stores a value in the store.
set(key: WeakKey | undefined, id: Key, value: Value): void
key
WeakKey | undefined
required
The weak key (object reference)
The identifier within that key’s map
clear
Clears all values associated with a key.
clear(key: WeakKey | undefined): void
key
WeakKey | undefined
required
The weak key to clear
Usage Example
import {WeakStore} from '@dnd-kit/state';
const store = new WeakStore<object, string, {data: string}>();
const obj1 = {};
const obj2 = {};
store.set(obj1, 'item-1', {data: 'value1'});
store.set(obj1, 'item-2', {data: 'value2'});
store.set(obj2, 'item-1', {data: 'value3'});
console.log(store.get(obj1, 'item-1')); // {data: 'value1'}
console.log(store.get(obj2, 'item-1')); // {data: 'value3'}
store.clear(obj1);
console.log(store.get(obj1, 'item-1')); // undefined
// When obj1 and obj2 are garbage collected,
// their associated data is automatically freed
Types
Signal
interface Signal<T> {
value: T;
peek(): T;
subscribe(fn: (value: T) => void): () => void;
}
ReadonlySignal
interface ReadonlySignal<T> {
readonly value: T;
peek(): T;
subscribe(fn: (value: T) => void): () => void;
}
Effect
type Effect = () => void | CleanupFunction;
CleanupFunction
type CleanupFunction = () => void;
WithHistory
type WithHistory<T> = {
current: T;
initial: T;
previous: T | undefined;
};