Skip to main content
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>
value
T
required
The initial value
signal
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>
compute
() => T
required
Function that computes the value
comparator
(a: T, b: T) => boolean
Optional equality comparator to prevent unnecessary updates
computed
ReadonlySignal<T>
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.
dispose
() => void
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
entries
Effect[]
required
Multiple effect functions to run
cleanup
CleanupFunction
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
fn
() => void
required
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
fn
() => T
required
Function that reads signals
result
T
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
enumerable
boolean
default:"true"
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
a
T
required
First value
b
T
required
Second value
equal
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
value
T extends object
required
The reactive object to snapshot
snapshot
T
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
  )
}
defaultValue
T
required
The default/initial value
equals
(a: T, b: T) => boolean
default:"Object.is"
Equality comparator for detecting changes

Properties

current
T
The current value (reactive)
previous
T | undefined
The previous value, or undefined if not yet changed (reactive)
initial
T
The initial value (reactive)

Methods

reset

Resets to the default value or a new value.
reset(value?: T): void

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)
id
Key
required
The identifier within that key’s map
value
Value | undefined
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)
id
Key
required
The identifier within that key’s map
value
Value
required
The value to store

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;
};

Build docs developers (and LLMs) love