CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-solid-js

A declarative JavaScript library for building user interfaces with fine-grained reactivity.

Pending
Overview
Eval results
Files

store-management.mddocs/

Store Management

Nested reactive state management system with proxy-based stores, mutations, and advanced reconciliation for managing complex application state.

Capabilities

Creating Stores

Create reactive stores for managing nested and complex state structures.

/**
 * Creates a reactive store that can be read through a proxy object and written with a setter function
 * @param store - Initial store value or existing store
 * @param options - Configuration options
 * @returns Tuple of [store getter proxy, store setter function]
 */
function createStore<T extends object = {}>(
  ...[store, options]: {} extends T
    ? [store?: T | Store<T>, options?: { name?: string }]
    : [store: T | Store<T>, options?: { name?: string }]
): [get: Store<T>, set: SetStoreFunction<T>];

/**
 * Returns the underlying data in the store without a proxy
 * @param item - Store or value to unwrap
 * @param set - Optional set to track unwrapped objects
 * @returns Unwrapped data
 */
function unwrap<T>(item: T, set?: Set<unknown>): T;

interface StoreNode {
  [$NODE]?: DataNodes;
  [key: PropertyKey]: any;
}

type NotWrappable = string | number | bigint | symbol | boolean | Function | null | undefined;
type Store<T> = T;

Usage Examples:

import { createStore, unwrap } from "solid-js/store";

// Basic store creation
const [user, setUser] = createStore({
  name: "John Doe",
  email: "john@example.com",
  profile: {
    age: 30,
    bio: "Software developer",
    preferences: {
      theme: "dark",
      notifications: true
    }
  },
  posts: []
});

// Reading from store (reactive)
console.log(user.name); // "John Doe"
console.log(user.profile.age); // 30

// Updating store values
setUser("name", "Jane Doe");
setUser("profile", "age", 31);
setUser("profile", "preferences", "theme", "light");

// Functional updates
setUser("profile", "age", age => age + 1);

// Adding to arrays
setUser("posts", posts => [...posts, { id: 1, title: "First Post" }]);

// Unwrapping store data (removes reactivity)
const rawUserData = unwrap(user);
console.log(rawUserData); // Plain JavaScript object

Advanced Store Operations

Perform complex updates with nested path setting and batch operations.

interface SetStoreFunction<T> {
  // Set entire store
  (value: T): void;
  (setter: (prev: T) => T): void;
  
  // Set by key
  <K extends keyof T>(key: K, value: T[K]): void;
  <K extends keyof T>(key: K, setter: (prev: T[K]) => T[K]): void;
  
  // Set by nested path
  <K1 extends keyof T, K2 extends keyof T[K1]>(
    key1: K1,
    key2: K2,
    value: T[K1][K2]
  ): void;
  
  // Array operations
  <K extends keyof T>(
    key: K,
    index: number,
    value: T[K] extends readonly (infer U)[] ? U : never
  ): void;
  
  // Conditional updates and more overloads...
}

Usage Examples:

import { createStore } from "solid-js/store";
import { For, createEffect } from "solid-js";

function TodoApp() {
  const [todos, setTodos] = createStore({
    items: [
      { id: 1, text: "Learn Solid", completed: false },
      { id: 2, text: "Build an app", completed: false }
    ],
    filter: "all" as "all" | "active" | "completed",
    stats: {
      total: 2,
      active: 2,
      completed: 0
    }
  });

  // Update individual todo
  const toggleTodo = (id: number) => {
    setTodos(
      "items",
      item => item.id === id,
      "completed",
      completed => !completed
    );
    updateStats();
  };

  // Add new todo
  const addTodo = (text: string) => {
    const newTodo = {
      id: Date.now(),
      text,
      completed: false
    };
    
    setTodos("items", items => [...items, newTodo]);
    updateStats();
  };

  // Remove todo
  const removeTodo = (id: number) => {
    setTodos("items", items => items.filter(item => item.id !== id));
    updateStats();
  };

  // Update statistics
  const updateStats = () => {
    setTodos("stats", {
      total: todos.items.length,
      active: todos.items.filter(item => !item.completed).length,
      completed: todos.items.filter(item => item.completed).length
    });
  };

  // Batch operations
  const clearCompleted = () => {
    setTodos("items", items => items.filter(item => !item.completed));
    updateStats();
  };

  const toggleAll = () => {
    const allCompleted = todos.items.every(item => item.completed);
    setTodos("items", {}, "completed", !allCompleted);
    updateStats();
  };

  return (
    <div class="todo-app">
      <h1>Todo App</h1>
      
      <div class="stats">
        <span>Total: {todos.stats.total}</span>
        <span>Active: {todos.stats.active}</span>
        <span>Completed: {todos.stats.completed}</span>
      </div>

      <div class="controls">
        <button onClick={toggleAll}>Toggle All</button>
        <button onClick={clearCompleted}>Clear Completed</button>
        <select 
          value={todos.filter} 
          onChange={(e) => setTodos("filter", e.target.value as any)}
        >
          <option value="all">All</option>
          <option value="active">Active</option>
          <option value="completed">Completed</option>
        </select>
      </div>

      <For each={filteredTodos()}>
        {(todo) => (
          <div class={`todo ${todo.completed ? "completed" : ""}`}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span>{todo.text}</span>
            <button onClick={() => removeTodo(todo.id)}>Remove</button>
          </div>
        )}
      </For>
    </div>
  );

  function filteredTodos() {
    switch (todos.filter) {
      case "active":
        return todos.items.filter(item => !item.completed);
      case "completed":
        return todos.items.filter(item => item.completed);
      default:
        return todos.items;
    }
  }
}

Mutable Stores

Create mutable stores that can be modified directly like plain objects.

/**
 * Creates a mutable store that can be mutated directly
 * @param state - Initial state
 * @param options - Configuration options
 * @returns Mutable store object
 */
function createMutable<T extends StoreNode>(
  state: T,
  options?: { name?: string }
): T;

/**
 * Modifies a mutable store using a modifier function
 * @param state - Mutable store to modify
 * @param modifier - Function that modifies the state (mutates in place)
 */
function modifyMutable<T>(
  state: T,
  modifier: (state: T) => void
): void;

Usage Examples:

import { createMutable, modifyMutable } from "solid-js/store";
import { createEffect } from "solid-js";

function MutableStoreExample() {
  const state = createMutable({
    user: {
      name: "John",
      age: 30,
      preferences: {
        theme: "dark",
        language: "en"
      }
    },
    items: [1, 2, 3]
  });

  // Direct mutation (triggers reactivity)
  const updateUser = () => {
    state.user.name = "Jane";
    state.user.age++;
    state.user.preferences.theme = state.user.preferences.theme === "dark" ? "light" : "dark";
  };

  // Array mutations
  const addItem = () => {
    state.items.push(state.items.length + 1);
  };

  const removeItem = () => {
    state.items.pop();
  };

  // Using modifyMutable for complex updates
  const resetState = () => {
    modifyMutable(state, (draft) => {
      draft.user.name = "Anonymous";
      draft.user.age = 0;
      draft.user.preferences = { theme: "light", language: "en" };
      draft.items = [];
      return draft;
    });
  };

  // Reactive effects work with mutable stores
  createEffect(() => {
    console.log("User changed:", state.user.name, state.user.age);
  });

  createEffect(() => {
    console.log("Items count:", state.items.length);
  });

  return (
    <div>
      <h2>Mutable Store Example</h2>
      
      <div>
        <p>User: {state.user.name} (Age: {state.user.age})</p>
        <p>Theme: {state.user.preferences.theme}</p>
        <p>Items: {state.items.join(", ")}</p>
      </div>

      <div>
        <button onClick={updateUser}>Update User</button>
        <button onClick={addItem}>Add Item</button>
        <button onClick={removeItem}>Remove Item</button>
        <button onClick={resetState}>Reset</button>
      </div>
    </div>
  );
}

Store Modifiers

Use advanced modifiers for efficient updates and reconciliation.

/**
 * Diff method for setStore to efficiently update nested data
 * @param value - New value to reconcile with
 * @param options - Reconciliation options (defaults to empty object)
 * @returns Function that performs the reconciliation
 */
function reconcile<T extends U, U>(
  value: T,
  options: {
    key?: string | null;
    merge?: boolean;
  } = {}
): (state: U) => T;

/**
 * Immer-style mutation helper for stores
 * @param fn - Function that mutates the draft state
 * @returns Function that applies the mutations
 */
function produce<T>(
  fn: (state: T) => void
): (state: T) => T;

Usage Examples:

import { createStore, reconcile, produce } from "solid-js/store";

function AdvancedStoreExample() {
  const [data, setData] = createStore({
    users: [
      { id: 1, name: "John", posts: [] },
      { id: 2, name: "Jane", posts: [] }
    ],
    settings: {
      theme: "dark",
      language: "en"
    }
  });

  // Reconcile with new data (efficient updates)
  const updateUsers = async () => {
    const newUsers = await fetchUsers(); // Assume this returns updated user data
    
    setData("users", reconcile(newUsers, { key: "id" }));
  };

  // Using produce for complex mutations
  const addPostToUser = (userId: number, post: { title: string; content: string }) => {
    setData(produce((draft) => {
      const user = draft.users.find(u => u.id === userId);
      if (user) {
        user.posts.push({ id: Date.now(), ...post });
      }
    }));
  };

  // Reconcile with merge option
  const updateSettings = (newSettings: Partial<typeof data.settings>) => {
    setData("settings", reconcile(newSettings, { merge: true }));
  };

  // Complex nested updates with produce
  const complexUpdate = () => {
    setData(produce((draft) => {
      // Multiple complex operations
      draft.users.forEach(user => {
        if (user.posts.length > 5) {
          user.posts = user.posts.slice(-5); // Keep only last 5 posts
        }
      });
      
      // Update settings
      draft.settings.theme = draft.settings.theme === "dark" ? "light" : "dark";
      
      // Add new user if needed
      if (draft.users.length < 10) {
        draft.users.push({
          id: Date.now(),
          name: `User ${draft.users.length + 1}`,
          posts: []
        });
      }
    }));
  };

  return (
    <div>
      <div class="controls">
        <button onClick={updateUsers}>Update Users</button>
        <button onClick={() => addPostToUser(1, { title: "New Post", content: "Content" })}>
          Add Post to User 1
        </button>
        <button onClick={() => updateSettings({ language: "es" })}>
          Change Language
        </button>
        <button onClick={complexUpdate}>Complex Update</button>
      </div>

      <div class="data">
        <h3>Users ({data.users.length})</h3>
        <For each={data.users}>
          {(user) => (
            <div class="user">
              <h4>{user.name}</h4>
              <p>Posts: {user.posts.length}</p>
            </div>
          )}
        </For>
        
        <h3>Settings</h3>
        <p>Theme: {data.settings.theme}</p>
        <p>Language: {data.settings.language}</p>
      </div>
    </div>
  );
}

async function fetchUsers() {
  // Simulate API call
  return [
    { id: 1, name: "John Updated", posts: [{ id: 1, title: "Post 1" }] },
    { id: 2, name: "Jane Updated", posts: [] },
    { id: 3, name: "New User", posts: [] }
  ];
}

Store Utilities and Debugging

Access raw data and debugging utilities for store management.

/**
 * Symbol for accessing raw store data
 */
const $RAW: unique symbol;

/**
 * Symbol for accessing store nodes
 */
const $NODE: unique symbol;

/**
 * Symbol for tracking store property access
 */
const $HAS: unique symbol;

/**
 * Symbol for self-reference in stores
 */
const $SELF: unique symbol;

/**
 * Checks if an object can be wrapped by the store proxy
 * @param obj - Object to check
 * @returns True if the object can be wrapped
 */
function isWrappable<T>(obj: T | NotWrappable): obj is T;

/**
 * Development utilities for stores
 */
const DEV: {
  $NODE: symbol;
  isWrappable: (obj: any) => boolean;
  hooks: {
    onStoreNodeUpdate: OnStoreNodeUpdate | null;
  };
} | undefined;

type OnStoreNodeUpdate = (node: any, property: string | number | symbol, value: any, prev: any) => void;

Usage Examples:

import { createStore, unwrap, $RAW } from "solid-js/store";

function StoreDebugging() {
  const [store, setStore] = createStore({
    nested: {
      data: { count: 0 },
      array: [1, 2, 3]
    }
  });

  // Access raw data using $RAW symbol
  console.log(store[$RAW]); // Raw underlying data

  // Unwrap for serialization
  const serializeStore = () => {
    const unwrapped = unwrap(store);
    return JSON.stringify(unwrapped, null, 2);
  };

  // Compare wrapped vs unwrapped
  const compareData = () => {
    console.log("Wrapped:", store.nested.data);
    console.log("Unwrapped:", unwrap(store.nested.data));
    console.log("Are same reference:", store.nested.data === unwrap(store.nested.data)); // false
  };

  // Development hooks (only available in dev mode)
  if (DEV) {
    DEV.hooks.onStoreNodeUpdate = (node, property, value, prev) => {
      console.log("Store update:", { node, property, value, prev });
    };
  }

  return (
    <div>
      <h2>Store Debugging</h2>
      
      <div>
        <p>Count: {store.nested.data.count}</p>
        <p>Array: [{store.nested.array.join(", ")}]</p>
      </div>

      <div>
        <button onClick={() => setStore("nested", "data", "count", c => c + 1)}>
          Increment Count
        </button>
        <button onClick={() => setStore("nested", "array", arr => [...arr, arr.length + 1])}>
          Add to Array
        </button>
        <button onClick={compareData}>
          Compare Data
        </button>
      </div>

      <div>
        <h3>Serialized Store:</h3>
        <pre>{serializeStore()}</pre>
      </div>
    </div>
  );
}

Internal Store Functions

Advanced internal functions exported for library authors and debugging.

/**
 * Gets data nodes from a store target
 * @param target - Store target object
 * @param symbol - Symbol to access ($NODE or $HAS)
 * @returns Data nodes for the target
 */
function getNodes(target: StoreNode, symbol: typeof $NODE | typeof $HAS): DataNodes;

/**
 * Gets a specific node from data nodes
 * @param nodes - Data nodes collection
 * @param property - Property key to access
 * @param value - Optional value for node creation
 * @returns Data node for the property
 */
function getNode(nodes: DataNodes, property: PropertyKey, value?: any): DataNode;

/**
 * Sets a property value on a store node
 * @param state - Store node to update
 * @param property - Property key to set
 * @param value - Value to set
 * @param deleting - Whether this is a deletion operation
 */
function setProperty(state: StoreNode, property: PropertyKey, value: any, deleting?: boolean): void;

/**
 * Updates a nested path in the store
 * @param current - Current store node
 * @param path - Path array to update
 * @param traversed - Properties already traversed
 */
function updatePath(current: StoreNode, path: any[], traversed?: PropertyKey[]): void;

Types

Store Types

type Store<T> = T;

interface StoreNode {
  [$NODE]?: DataNodes;
  [key: PropertyKey]: any;
}

type NotWrappable = string | number | bigint | symbol | boolean | Function | null | undefined;

type DataNodes = Record<PropertyKey, DataNode>;

interface DataNode {
  value: any;
  listeners?: Set<Function>;
}

interface SetStoreFunction<T> {
  // Root level updates
  (value: T): void;
  (setter: (prev: T) => T): void;
  
  // Key-based updates
  <K extends keyof T>(key: K, value: T[K]): void;
  <K extends keyof T>(key: K, setter: (prev: T[K]) => T[K]): void;
  
  // Nested path updates (multiple overloads for different depths)
  <K1 extends keyof T, K2 extends keyof T[K1]>(
    key1: K1,
    key2: K2,
    value: T[K1][K2]
  ): void;
  
  // Conditional updates
  <K extends keyof T>(
    key: K,
    predicate: (item: T[K], index: number) => boolean,
    value: T[K]
  ): void;
}

Reconcile Types

interface ReconcileOptions {
  key?: string | null;
  merge?: boolean;
}

type ReconcileFunction<T, U = T> = (state: U) => T;

Install with Tessl CLI

npx tessl i tessl/npm-solid-js

docs

component-system.md

context-scoping.md

control-flow.md

index.md

reactive-primitives.md

resources-async.md

store-management.md

web-rendering.md

tile.json