Proxy-state management library that makes state simple for React and vanilla JavaScript applications
—
Extended functionality including reactive effects, key-specific subscriptions, DevTools integration, deep cloning, and specialized collections for Maps and Sets.
Creates a reactive effect that automatically tracks proxy objects and re-evaluates whenever tracked objects update. Provides Vue.js-style computed effects for Valtio.
/**
* Creates a reactive effect that automatically tracks proxy objects
* Callback is invoked immediately to detect tracked objects
* @param callback - Function that receives a get function for tracking proxy objects
* @param options - Configuration options
* @param options.sync - If true, notifications happen synchronously
* @returns Cleanup function to stop the reactive effect
*/
function watch(
callback: (get: <T extends object>(proxyObject: T) => T) => void | (() => void) | Promise<void | (() => void)>,
options?: { sync?: boolean }
): () => void;Usage Examples:
import { proxy, watch } from "valtio/utils";
const state = proxy({ count: 0, multiplier: 2 });
// Basic reactive effect
const cleanup = watch((get) => {
const { count, multiplier } = get(state);
console.log("Computed value:", count * multiplier);
});
state.count = 5; // Logs: "Computed value: 10"
state.multiplier = 3; // Logs: "Computed value: 15"
// Effect with cleanup
const cleanupWithSideEffect = watch((get) => {
const { count } = get(state);
// Set up side effect
const timer = setInterval(() => {
console.log("Current count:", count);
}, 1000);
// Return cleanup function
return () => clearInterval(timer);
});
// Nested watch calls are automatically cleaned up
watch((get) => {
const { user } = get(appState);
if (user) {
// This watch will be cleaned up when user changes
const innerCleanup = watch((get) => {
const { preferences } = get(user);
console.log("User preferences:", preferences);
});
}
});
// Stop watching
cleanup();Subscribes to changes of a specific property key, providing more efficient notifications than general subscriptions.
/**
* Subscribes to a specific property key of a proxy object
* Only fires when the specified property changes
* @param proxyObject - The proxy object to subscribe to
* @param key - The property key to watch
* @param callback - Function called when the key changes
* @param notifyInSync - If true, notifications happen synchronously
* @returns Unsubscribe function
*/
function subscribeKey<T extends object, K extends keyof T>(
proxyObject: T,
key: K,
callback: (value: T[K]) => void,
notifyInSync?: boolean
): () => void;Usage Examples:
import { proxy, subscribeKey } from "valtio/utils";
const state = proxy({ count: 0, name: "Alice", theme: "light" });
// Subscribe to specific key
const unsubscribe = subscribeKey(state, "count", (newCount) => {
console.log("Count changed to:", newCount);
});
state.count++; // Logs: "Count changed to: 1"
state.name = "Bob"; // No log (different key)
state.count = 5; // Logs: "Count changed to: 5"
// Multiple subscriptions to same key
const unsub1 = subscribeKey(state, "theme", (theme) => {
document.body.className = theme;
});
const unsub2 = subscribeKey(state, "theme", (theme) => {
localStorage.setItem("theme", theme);
});
state.theme = "dark"; // Both callbacks fire
// Cleanup
unsubscribe();
unsub1();
unsub2();Connects a proxy object to Redux DevTools Extension for state debugging and time-travel debugging.
/**
* Connects a proxy object to Redux DevTools Extension
* Enables real-time monitoring and time-travel debugging
* Limitation: Only plain objects/values are supported
* @param proxyObject - The proxy object to connect to DevTools
* @param options - Configuration options for the DevTools connection
* @param options.enabled - Explicitly enable or disable the connection
* @param options.name - Name to display in DevTools
* @returns Unsubscribe function or undefined if connection failed
*/
function devtools<T extends object>(
proxyObject: T,
options?: {
enabled?: boolean;
name?: string;
[key: string]: any; // Additional Redux DevTools options
}
): (() => void) | undefined;Usage Examples:
import { proxy, devtools } from "valtio/utils";
const state = proxy({ count: 0, user: { name: "Alice" } });
// Basic DevTools connection
const disconnect = devtools(state, {
name: "App State",
enabled: true
});
// DevTools will show:
// - Current state snapshots
// - Action history with operation details
// - Time-travel debugging capabilities
state.count++; // Shows as "set:count" in DevTools
state.user.name = "Bob"; // Shows as "set:user.name" in DevTools
// Multiple stores
const userState = proxy({ profile: {}, settings: {} });
const appState = proxy({ theme: "light", language: "en" });
devtools(userState, { name: "User Store" });
devtools(appState, { name: "App Store" });
// Conditional enabling
devtools(state, {
enabled: process.env.NODE_ENV === "development",
name: "Debug State"
});
// Cleanup
disconnect?.();Creates a deep clone of an object while maintaining proxy behavior for Maps and Sets.
/**
* Creates a deep clone of an object, maintaining proxy behavior for Maps and Sets
* @param obj - The object to clone
* @param getRefSet - Function to get the set of reference objects (optional)
* @returns A deep clone of the input object
*/
function deepClone<T>(
obj: T,
getRefSet?: () => WeakSet<object>
): T;Usage Examples:
import { proxy, deepClone, ref } from "valtio";
import { deepClone } from "valtio/utils";
const originalState = proxy({
user: { name: "Alice", preferences: { theme: "dark" } },
items: [1, 2, { nested: true }],
metadata: ref({ immutable: "data" })
});
// Deep clone the state
const clonedState = deepClone(originalState);
// Cloned state is independent
clonedState.user.name = "Bob";
console.log(originalState.user.name); // Still "Alice"
// Referenced objects are preserved
console.log(clonedState.metadata === originalState.metadata); // true
// Works with complex structures
const complexState = proxy({
map: new Map([["key", "value"]]),
set: new Set([1, 2, 3]),
date: new Date(),
nested: { deep: { structure: "value" } }
});
const cloned = deepClone(complexState);Creates a reactive Set that integrates with Valtio's proxy system, extending the standard Set API with additional set operations.
/**
* Creates a reactive Set that integrates with Valtio's proxy system
* Includes extended set operations like union, intersection, difference
* @param initialValues - Initial values to populate the Set
* @returns A reactive proxy Set with extended methods
* @throws TypeError if initialValues is not iterable
*/
function proxySet<T>(initialValues?: Iterable<T> | null): ProxySet<T>;
/**
* Determines if an object is a proxy Set created with proxySet
* @param obj - The object to check
* @returns True if the object is a proxy Set
*/
function isProxySet(obj: object): boolean;Usage Examples:
import { proxy, useSnapshot } from "valtio";
import { proxySet, isProxySet } from "valtio/utils";
// Basic usage
const tags = proxySet(["javascript", "react", "typescript"]);
// Use within proxy state
const state = proxy({
selectedTags: proxySet<string>(),
allTags: proxySet(["javascript", "react", "vue", "typescript"])
});
// React component
function TagSelector() {
const { selectedTags, allTags } = useSnapshot(state);
return (
<div>
{[...allTags].map(tag => (
<button
key={tag}
onClick={() => {
if (state.selectedTags.has(tag)) {
state.selectedTags.delete(tag);
} else {
state.selectedTags.add(tag);
}
}}
className={selectedTags.has(tag) ? "selected" : ""}
>
{tag}
</button>
))}
</div>
);
}
// Extended set operations
const set1 = proxySet([1, 2, 3, 4]);
const set2 = proxySet([3, 4, 5, 6]);
const union = set1.union(set2); // {1, 2, 3, 4, 5, 6}
const intersection = set1.intersection(set2); // {3, 4}
const difference = set1.difference(set2); // {1, 2}
const symmetric = set1.symmetricDifference(set2); // {1, 2, 5, 6}
console.log(set1.isSubsetOf(set2)); // false
console.log(set1.isSupersetOf(new Set([1, 2]))); // true
console.log(set1.isDisjointFrom(new Set([7, 8]))); // true
// Type checking
console.log(isProxySet(set1)); // true
console.log(isProxySet(new Set())); // falseCreates a reactive Map that integrates with Valtio's proxy system with the same API as standard JavaScript Map.
/**
* Creates a reactive Map that integrates with Valtio's proxy system
* The API is the same as the standard JavaScript Map
* @param entries - Initial key-value pairs to populate the Map
* @returns A proxy Map object that tracks changes
* @throws TypeError if entries is not iterable
*/
function proxyMap<K, V>(entries?: Iterable<[K, V]> | null): ProxyMap<K, V>;
/**
* Determines if an object is a proxy Map created with proxyMap
* @param obj - The object to check
* @returns True if the object is a proxy Map
*/
function isProxyMap(obj: object): boolean;Usage Examples:
import { proxy, useSnapshot, ref } from "valtio";
import { proxyMap, isProxyMap } from "valtio/utils";
// Basic usage
const cache = proxyMap<string, any>();
// Use within proxy state
const state = proxy({
userCache: proxyMap<number, User>(),
settings: proxyMap([
["theme", "dark"],
["language", "en"]
])
});
// React component
function UserList() {
const { userCache } = useSnapshot(state);
return (
<div>
{[...userCache.entries()].map(([id, user]) => (
<div key={id}>
{user.name}
<button onClick={() => state.userCache.delete(id)}>
Remove
</button>
</div>
))}
</div>
);
}
// Standard Map operations
state.userCache.set(1, { name: "Alice", age: 25 });
state.userCache.set(2, { name: "Bob", age: 30 });
console.log(state.userCache.get(1)); // { name: "Alice", age: 25 }
console.log(state.userCache.size); // 2
// Using object keys with ref
const objKey = ref({ id: "special" });
state.userCache.set(objKey, { name: "Special User", age: 35 });
// Without ref, object keys might not work as expected
const badKey = { id: "bad" };
state.userCache.set(badKey, { name: "Bad User", age: 40 });
console.log(state.userCache.get(badKey)); // undefined (key equality issue)
// Iteration
for (const [key, value] of state.userCache) {
console.log(`User ${key}:`, value.name);
}
// Type checking
console.log(isProxyMap(state.userCache)); // true
console.log(isProxyMap(new Map())); // falsetype WatchCallback = (
get: <T extends object>(proxyObject: T) => T
) => void | (() => void) | Promise<void | (() => void)>;
type WatchOptions = {
sync?: boolean;
};
type Cleanup = () => void;
interface DevToolsOptions {
enabled?: boolean;
name?: string;
[key: string]: any;
}
// Extended collection interfaces
interface ProxySet<T> extends Set<T> {
// Extended set operations
intersection(other: Set<T>): Set<T>;
union(other: Set<T>): Set<T>;
difference(other: Set<T>): Set<T>;
symmetricDifference(other: Set<T>): Set<T>;
// Set comparison methods
isSubsetOf(other: Set<T>): boolean;
isSupersetOf(other: Set<T>): boolean;
isDisjointFrom(other: Set<T>): boolean;
// Additional methods
toJSON(): Set<T>;
}
interface ProxyMap<K, V> extends Map<K, V> {
toJSON(): Map<K, V>;
}
// Additional internal types
type AnyFunction = (...args: any[]) => any;
type ProxyObject = object;
type Path = (string | symbol)[];Install with Tessl CLI
npx tessl i tessl/npm-valtio