CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-valtio

Proxy-state management library that makes state simple for React and vanilla JavaScript applications

Pending
Overview
Eval results
Files

utilities.mddocs/

Advanced Utilities

Extended functionality including reactive effects, key-specific subscriptions, DevTools integration, deep cloning, and specialized collections for Maps and Sets.

Capabilities

Reactive Effects (watch)

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();

Key-Specific Subscriptions

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();

Redux DevTools Integration

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?.();

Deep Cloning

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

Proxy Sets

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())); // false

Proxy Maps

Creates 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())); // false

Utility Types

type 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

docs

core-proxy.md

index.md

react-integration.md

utilities.md

tile.json