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

reactive-primitives.mddocs/

Reactive Primitives

Core reactive system providing signals, effects, and memos for building reactive applications with automatic dependency tracking and fine-grained updates.

Capabilities

Signals

Create reactive state with getter and setter functions that automatically track dependencies and trigger updates.

/**
 * Creates a reactive state with getter and setter functions
 * @param value - Initial value for the signal
 * @param options - Configuration options for the signal
 * @returns Tuple of [getter, setter] functions
 */
function createSignal<T>(): Signal<T | undefined>;
function createSignal<T>(value: T, options?: SignalOptions<T>): Signal<T>;

interface SignalOptions<T> {
  equals?: false | ((prev: T, next: T) => boolean);
  name?: string;
  internal?: boolean;
}

Usage Examples:

import { createSignal } from "solid-js";

// Basic signal
const [count, setCount] = createSignal(0);
console.log(count()); // 0
setCount(5);
console.log(count()); // 5

// Signal with custom equality
const [user, setUser] = createSignal(
  { name: "John", age: 30 },
  { equals: (prev, next) => prev.name === next.name && prev.age === next.age }
);

// Functional updates
setCount(prev => prev + 1);
setUser(prev => ({ ...prev, age: prev.age + 1 }));

Effects

Create reactive computations that run automatically when their dependencies change.

/**
 * Creates a reactive computation that runs after render phase
 * @param fn - Effect function that tracks dependencies
 * @param value - Initial value for the effect
 * @param options - Configuration options
 */
function createEffect<T>(fn: EffectFunction<T>, value?: T, options?: EffectOptions): void;

/**
 * Creates a reactive computation during render phase
 * @param fn - Effect function that tracks dependencies
 * @param value - Initial value for the effect
 * @param options - Configuration options
 */
function createRenderEffect<T>(fn: EffectFunction<T>, value?: T, options?: EffectOptions): void;

/**
 * Creates a reactive computation that runs immediately before render
 * @param fn - Effect function that tracks dependencies
 * @param value - Initial value for the effect
 * @param options - Configuration options
 */
function createComputed<T>(fn: EffectFunction<T>, value?: T, options?: EffectOptions): void;

type EffectFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;

interface EffectOptions {
  name?: string;
}

Usage Examples:

import { createSignal, createEffect } from "solid-js";

const [count, setCount] = createSignal(0);

// Basic effect
createEffect(() => {
  console.log("Count is:", count());
});

// Effect with previous value
createEffect((prev) => {
  const current = count();
  console.log(`Count changed from ${prev} to ${current}`);
  return current;
}, count());

// Effect with cleanup
createEffect(() => {
  const timer = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
  
  onCleanup(() => clearInterval(timer));
});

Memos

Create derived reactive values that are memoized and only recalculate when their dependencies change.

/**
 * Creates a readonly derived reactive memoized signal
 * @param fn - Memo function that computes the derived value
 * @param value - Initial value for the memo
 * @param options - Configuration options
 * @returns Accessor function for the memoized value
 */
function createMemo<T>(fn: EffectFunction<T>, value?: T, options?: MemoOptions<T>): Accessor<T>;

interface MemoOptions<T> extends EffectOptions {
  equals?: false | ((prev: T, next: T) => boolean);
}

Usage Examples:

import { createSignal, createMemo } from "solid-js";

const [firstName, setFirstName] = createSignal("John");
const [lastName, setLastName] = createSignal("Doe");

// Basic memo
const fullName = createMemo(() => `${firstName()} ${lastName()}`);

// Memo with custom equality
const expensiveComputation = createMemo(
  () => someExpensiveFunction(firstName(), lastName()),
  undefined,
  { equals: (prev, next) => prev.id === next.id }
);

// Using memos in components
function UserProfile() {
  const displayName = createMemo(() => {
    const first = firstName();
    const last = lastName();
    return first && last ? `${first} ${last}` : first || last || "Anonymous";
  });

  return <div>Hello, {displayName()}!</div>;
}

Advanced Reactive Utilities

Create complex reactive patterns and manage reactive ownership.

/**
 * Creates a reactive computation with flexible tracking
 * @param onInvalidate - Function called when dependencies change
 * @param options - Configuration options
 * @returns Function to run reactive computations
 */
function createReaction(
  onInvalidate: () => void,
  options?: EffectOptions
): (fn: () => void) => void;

/**
 * Creates a reactive computation that only runs when browser is idle
 * @param source - Source accessor to watch
 * @param options - Configuration options
 * @returns Deferred accessor
 */
function createDeferred<T>(
  source: Accessor<T>,
  options?: DeferredOptions<T>
): Accessor<T>;

/**
 * Creates a conditional signal for O(2) instead of O(n) operations
 * @param source - Source accessor to watch
 * @param fn - Equality function for comparison
 * @param options - Configuration options
 * @returns Function that checks if key matches current value
 */
function createSelector<T, U>(
  source: Accessor<T>,
  fn?: (a: U, b: T) => boolean,
  options?: BaseOptions
): (key: U) => boolean;

interface DeferredOptions<T> {
  timeoutMs?: number;
  equals?: false | ((prev: T, next: T) => boolean);
}

Control Flow Utilities

Control reactive execution and dependency tracking.

/**
 * Batches reactive updates within the block
 * @param fn - Function to run in batch
 * @returns Return value of the function
 */
function batch<T>(fn: () => T): T;

/**
 * Ignores tracking context inside its scope
 * @param fn - Function to run without tracking
 * @returns Return value of the function
 */
function untrack<T>(fn: () => T): T;

/**
 * Makes dependencies of a computation explicit
 * @param deps - Dependencies to track
 * @param fn - Function to run when dependencies change
 * @param options - Configuration options
 * @returns Return value of the function
 */
function on<S, T>(
  deps: Accessor<S> | Accessor<S>[],
  fn: (input: S, prevInput?: S, prevValue?: T) => T,
  options?: OnOptions
): EffectFunction<undefined, T>;

interface OnOptions {
  defer?: boolean;
}

Lifecycle Hooks

Manage component lifecycle and cleanup in reactive computations.

/**
 * Runs an effect only after initial render on mount
 * @param fn - Function to run on mount
 */
function onMount(fn: () => void): void;

/**
 * Runs an effect once before the reactive scope is disposed
 * @param fn - Function to run on cleanup
 * @returns The cleanup function
 */
function onCleanup<T extends () => any>(fn: T): T;

/**
 * Runs an effect whenever an error is thrown within child scopes
 * @param fn - Function to run with the caught error
 * @param handler - Error handler function
 * @returns Return value or undefined if error occurred
 */
function catchError<T>(
  fn: () => T,
  handler: (err: Error) => void
): T | undefined;

Usage Examples:

import { createSignal, createEffect, onMount, onCleanup, batch } from "solid-js";

function Timer() {
  const [time, setTime] = createSignal(0);
  const [isRunning, setIsRunning] = createSignal(false);

  onMount(() => {
    console.log("Timer component mounted");
  });

  createEffect(() => {
    if (isRunning()) {
      const interval = setInterval(() => {
        setTime(t => t + 1);
      }, 1000);

      onCleanup(() => {
        clearInterval(interval);
        console.log("Timer cleanup");
      });
    }
  });

  const reset = () => {
    batch(() => {
      setTime(0);
      setIsRunning(false);
    });
  };

  return (
    <div>
      <div>Time: {time()}s</div>
      <button onClick={() => setIsRunning(!isRunning())}>
        {isRunning() ? "Stop" : "Start"}
      </button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Advanced Integration

Enable integration with external reactive systems and custom scheduling.

/**
 * Enables integration with external reactive systems
 * @param factory - Factory function for external sources
 * @param untrack - Function to untrack dependencies (defaults to provided untrack)
 */
function enableExternalSource(
  factory: ExternalSourceFactory,
  untrack?: <V>(fn: () => V) => V
): void;

/**
 * Enables custom scheduling for updates
 * @param scheduler - Custom scheduler function (defaults to requestCallback)
 */
function enableScheduling(scheduler?: (fn: () => void) => void): void;

type ExternalSourceFactory = <T>(
  fn: (track: Accessor<T>) => void,
  trigger: () => void
) => () => void;

Reactive Ownership and Scoping

Create and manage reactive ownership contexts for proper cleanup and component isolation.

/**
 * Creates a reactive root context for ownership management
 * @param fn - Function to run in the root context, receives dispose function
 * @param detachedOwner - Optional owner to detach from
 * @returns Return value of the function
 */
function createRoot<T>(fn: (dispose: () => void) => T, detachedOwner?: Owner): T;

/**
 * Gets the current reactive owner context
 * @returns Current owner or null if none
 */
function getOwner(): Owner | null;

/**
 * Runs a function within a specific owner context
 * @param owner - Owner context to run within
 * @param fn - Function to run
 * @returns Return value of the function
 */
function runWithOwner<T>(owner: Owner | null, fn: () => T): T | undefined;

/**
 * Resolves children in a reactive context
 * @param fn - Function that returns children
 * @returns Resolved children accessor
 */
function children(fn: () => any): ChildrenReturn;

type RootFunction<T> = (dispose: () => void) => T;
type ChildrenReturn = Accessor<ResolvedChildren> & { toArray: () => ResolvedJSXElement[] };

Usage Examples:

import { createRoot, createSignal, createEffect, getOwner, runWithOwner } from "solid-js";

// Basic root usage
const dispose = createRoot((dispose) => {
  const [count, setCount] = createSignal(0);
  
  createEffect(() => {
    console.log("Count:", count());
  });
  
  // Clean up when needed
  setTimeout(() => dispose(), 5000);
  
  return dispose;
});

// Owner management
function ParentComponent() {
  const owner = getOwner();
  
  const childComputation = () => {
    // This will run in the parent's context
    runWithOwner(owner, () => {
      createEffect(() => {
        console.log("Child effect in parent context");
      });
    });
  };
  
  return <div onClick={childComputation}>Click me</div>;
}

Context System

Create and consume context for passing data through the component tree.

/**
 * Creates a context for passing data through component tree
 * @param defaultValue - Default value when no provider is found
 * @param options - Optional configuration
 * @returns Context object with id and Provider
 */
function createContext<T>(defaultValue?: T, options?: EffectOptions): Context<T>;

/**
 * Consumes a context value from the nearest provider
 * @param context - Context to consume
 * @returns Current context value
 */
function useContext<T>(context: Context<T>): T;

interface Context<T> {
  id: symbol;
  Provider: ContextProviderComponent<T>;
  defaultValue: T;
}

type ContextProviderComponent<T> = Component<{
  value: T;
  children: JSX.Element;
}>;

Usage Examples:

import { createContext, useContext } from "solid-js";

// Create theme context
const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <UserInterface />
    </ThemeContext.Provider>
  );
}

function UserInterface() {
  const theme = useContext(ThemeContext);
  
  return (
    <div class={theme === "dark" ? "dark-theme" : "light-theme"}>
      Current theme: {theme}
    </div>
  );
}

Async Resources

Handle asynchronous data loading with reactive resources.

/**
 * Creates a resource for handling async data
 * @param fetcher - Function to fetch the resource data
 * @param options - Configuration options
 * @returns Resource tuple [resource accessor, resource actions]
 */
function createResource<T, R = unknown>(
  fetcher: ResourceFetcher<true, T, R>,
  options?: ResourceOptions<T, true>
): ResourceReturn<T, R>;

/**
 * Creates a resource with a source signal
 * @param source - Source signal that triggers refetch
 * @param fetcher - Function to fetch the resource data
 * @param options - Configuration options
 * @returns Resource tuple [resource accessor, resource actions]
 */
function createResource<T, S, R = unknown>(
  source: ResourceSource<S>,
  fetcher: ResourceFetcher<S, T, R>,
  options?: ResourceOptions<T, S>
): ResourceReturn<T, R>;

type ResourceSource<S> = S | false | null | undefined | (() => S | false | null | undefined);
type ResourceFetcher<S, T, R = unknown> = (source: S, info: ResourceFetcherInfo<T, R>) => T | Promise<T>;

interface ResourceFetcherInfo<T, R = unknown> {
  value: T | undefined;
  refetching: R | boolean;
}

interface ResourceOptions<T, S = unknown> {
  initialValue?: T;
  name?: string;
  deferStream?: boolean;
  ssrLoadFrom?: "initial" | "server";
  storage?: (init: T | undefined) => [Accessor<T | undefined>, Setter<T | undefined>];
  onHydrated?: (k: S | undefined, info: { value: T | undefined }) => void;
}

type ResourceReturn<T, R = unknown> = [Resource<T>, ResourceActions<T | undefined, R>];

type ResourceActions<T, R = unknown> = {
  mutate: Setter<T>;
  refetch: (info?: R) => T | Promise<T> | undefined | null;
};

Transitions

Manage async state transitions and provide loading states.

/**
 * Starts a transition and returns a promise
 * @param fn - Function to run in transition
 * @returns Promise that resolves when transition completes
 */
function startTransition(fn: () => unknown): Promise<void>;

/**
 * Hook for managing transition state
 * @returns Tuple of [isPending accessor, startTransition function]
 */
function useTransition(): [Accessor<boolean>, (fn: () => void) => Promise<void>];

type Transition = [Accessor<boolean>, (fn: () => void) => Promise<void>];

Usage Examples:

import { createResource, startTransition, useTransition } from "solid-js";

// Basic resource
const [user] = createResource(fetchUser);

// Resource with source
const [userId, setUserId] = createSignal(1);
const [userData] = createResource(userId, fetchUserById);

// Transitions
function App() {
  const [isPending, start] = useTransition();
  
  const handleUpdate = () => {
    start(() => {
      // This will show pending state
      updateSomething();
    });
  };
  
  return (
    <div>
      <button onClick={handleUpdate} disabled={isPending()}>
        {isPending() ? "Updating..." : "Update"}
      </button>
    </div>
  );
}

Types

Core Signal Types

type Accessor<T> = () => T;
type Setter<T> = {
  (value: T): T;
  (fn: (prev: T) => T): T;
};
type Signal<T> = [get: Accessor<T>, set: Setter<T>];

Reactive Ownership Types

interface Owner {
  owned: Computation<any>[] | null;
  cleanups: (() => void)[] | null;
  owner: Owner | null;
  context: any;
  sourceMap?: Set<Computation<any>>;
  name?: string;
}

interface Computation<T> {
  fn: EffectFunction<any, T>;
  state: number;
  sources: Computation<any>[] | null;
  sourceSlots: number[] | null;
  value?: T;
  age: number;
  updatedAt?: number;
}

Resource Types

type Resource<T> = Accessor<T | undefined> & {
  loading: boolean;
  error: any;
  latest: T | undefined;
  state: "unresolved" | "pending" | "ready" | "refreshing" | "errored";
};

type ResolvedJSXElement = Exclude<JSX.Element, JSX.ArrayElement>;
type ResolvedChildren = ResolvedJSXElement | ResolvedJSXElement[];

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