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

context-scoping.mddocs/

Context and Scoping

Context API for passing data through the component tree and scoping utilities for managing reactive ownership and cleanup.

Capabilities

Context Creation and Usage

Create and consume context for passing data through component trees without prop drilling.

/**
 * Creates a Context to handle state scoped for component children
 * @param defaultValue - Default value for the context
 * @returns Context object with Provider component
 */
function createContext<T>(defaultValue?: T): Context<T>;

/**
 * Uses a context to receive scoped state from parent's Context.Provider
 * @param context - Context object 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> = FlowComponent<{ value: T }>;

Usage Examples:

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

// Create theme context
interface Theme {
  primary: string;
  secondary: string;
  mode: "light" | "dark";
}

const ThemeContext = createContext<Theme>({
  primary: "#007bff",
  secondary: "#6c757d", 
  mode: "light"
});

// Theme provider component
const ThemeProvider: ParentComponent<{ theme: Theme }> = (props) => {
  return (
    <ThemeContext.Provider value={props.theme}>
      {props.children}
    </ThemeContext.Provider>
  );
};

// Component that uses theme context
function ThemedButton(props: { children: JSX.Element; onClick?: () => void }) {
  const theme = useContext(ThemeContext);
  
  return (
    <button 
      style={{
        "background-color": theme.primary,
        color: theme.mode === "dark" ? "white" : "black",
        border: `1px solid ${theme.secondary}`
      }}
      onClick={props.onClick}
    >
      {props.children}
    </button>
  );
}

// App with theme context
function App() {
  const [isDark, setIsDark] = createSignal(false);
  
  const theme = () => ({
    primary: isDark() ? "#bb86fc" : "#007bff",
    secondary: isDark() ? "#03dac6" : "#6c757d",
    mode: isDark() ? "dark" as const : "light" as const
  });

  return (
    <ThemeProvider theme={theme()}>
      <div style={{ 
        "background-color": isDark() ? "#121212" : "#ffffff",
        "min-height": "100vh",
        padding: "20px"
      }}>
        <h1>Themed App</h1>
        <ThemedButton onClick={() => setIsDark(!isDark())}>
          Toggle {isDark() ? "Light" : "Dark"} Mode
        </ThemedButton>
      </div>
    </ThemeProvider>
  );
}

Advanced Context Patterns

Create multiple contexts and nested providers for complex state management.

Usage Examples:

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

// User context
interface User {
  id: number;
  name: string;
  role: "admin" | "user";
}

const UserContext = createContext<User | null>(null);

// Settings context
interface Settings {
  language: string;
  notifications: boolean;
  theme: "light" | "dark";
}

const SettingsContext = createContext<Settings>({
  language: "en",
  notifications: true,
  theme: "light"
});

// Combined app context provider
function AppProviders(props: { children: JSX.Element }) {
  const [user, setUser] = createSignal<User | null>(null);
  const [settings, setSettings] = createSignal<Settings>({
    language: "en",
    notifications: true,
    theme: "light"
  });

  // Login function available through context
  const userActions = {
    login: (userData: User) => setUser(userData),
    logout: () => setUser(null),
    updateSettings: (newSettings: Partial<Settings>) => 
      setSettings(prev => ({ ...prev, ...newSettings }))
  };

  return (
    <UserContext.Provider value={user()}>
      <SettingsContext.Provider value={settings()}>
        {/* We can also create an actions context */}
        <ActionsContext.Provider value={userActions}>
          {props.children}
        </ActionsContext.Provider>
      </SettingsContext.Provider>
    </UserContext.Provider>
  );
}

// Custom hooks for easier context consumption
function useUser() {
  const user = useContext(UserContext);
  const isAuthenticated = createMemo(() => user !== null);
  const isAdmin = createMemo(() => user?.role === "admin");
  
  return { user, isAuthenticated: isAuthenticated(), isAdmin: isAdmin() };
}

function useSettings() {
  return useContext(SettingsContext);
}

// Component using multiple contexts
function UserDashboard() {
  const { user, isAuthenticated, isAdmin } = useUser();
  const settings = useSettings();
  const actions = useContext(ActionsContext);

  return (
    <div>
      <Show when={isAuthenticated} fallback={<LoginForm />}>
        <div>
          <h1>Welcome, {user!.name}!</h1>
          <p>Language: {settings.language}</p>
          <p>Theme: {settings.theme}</p>
          
          <Show when={isAdmin}>
            <AdminPanel />
          </Show>
          
          <button onClick={actions.logout}>Logout</button>
        </div>
      </Show>
    </div>
  );
}

Reactive Ownership and Scoping

Manage reactive ownership and create isolated reactive scopes.

/**
 * Creates a new non-tracked reactive context that doesn't auto-dispose
 * @param fn - Function to run in the new reactive scope
 * @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
 * @returns Current owner or null
 */
function getOwner(): Owner | null;

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

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

Usage Examples:

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

// Creating isolated reactive scopes
function IsolatedComponent() {
  let dispose: (() => void) | undefined;

  const cleanup = () => {
    if (dispose) {
      dispose();
      dispose = undefined;
    }
  };

  const initialize = () => {
    cleanup(); // Clean up previous scope if it exists
    
    createRoot((disposeScope) => {
      dispose = disposeScope;
      
      const [count, setCount] = createSignal(0);
      
      createEffect(() => {
        console.log("Isolated count:", count());
      });
      
      // Set up some interval that will be cleaned up
      const interval = setInterval(() => {
        setCount(c => c + 1);
      }, 1000);
      
      onCleanup(() => {
        clearInterval(interval);
        console.log("Isolated scope cleaned up");
      });
      
      return { count, setCount };
    });
  };

  onCleanup(cleanup);

  return (
    <div>
      <button onClick={initialize}>Initialize Isolated Scope</button>
      <button onClick={cleanup}>Cleanup Scope</button>
    </div>
  );
}

// Working with owners for advanced patterns
function OwnerExample() {
  const [items, setItems] = createSignal<{ id: number; owner: Owner | null }[]>([]);

  const addItem = () => {
    const owner = getOwner();
    setItems(prev => [...prev, { id: Date.now(), owner }]);
  };

  const runInOriginalOwner = (item: { id: number; owner: Owner | null }) => {
    runWithOwner(item.owner, () => {
      console.log("Running in original owner context");
      // This runs in the context where the item was created
    });
  };

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <For each={items()}>
        {(item) => (
          <div>
            Item {item.id}
            <button onClick={() => runInOriginalOwner(item)}>
              Run in Original Context
            </button>
          </div>
        )}
      </For>
    </div>
  );
}

Transitions and Scheduling

Manage reactive updates with transitions and custom scheduling.

/**
 * Wraps updates in a low-priority transition
 * @param fn - Function containing updates to run in transition
 * @returns Promise that resolves when transition completes
 */
function startTransition(fn: () => void): Promise<void>;

/**
 * Returns a tuple with pending accessor and startTransition function
 * @returns Tuple of [isPending accessor, startTransition function]
 */
function useTransition(): [Accessor<boolean>, (fn: () => void) => Promise<void>];

Usage Examples:

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

function TransitionExample() {
  const [query, setQuery] = createSignal("");
  const [items, setItems] = createSignal(generateItems(1000));
  const [isPending, startTransition] = useTransition();

  // Expensive filtering operation
  const filteredItems = createMemo(() => {
    const q = query().toLowerCase();
    return items().filter(item => 
      item.name.toLowerCase().includes(q) ||
      item.description.toLowerCase().includes(q)
    );
  });

  const handleSearch = (value: string) => {
    // Update query immediately for responsive input
    setQuery(value);
    
    // Wrap expensive filtering in transition
    startTransition(() => {
      // This could trigger expensive computations
      console.log("Filtering items...");
    });
  };

  return (
    <div>
      <input
        type="text"
        placeholder="Search items..."
        value={query()}
        onInput={(e) => handleSearch(e.target.value)}
      />
      
      <div>
        {isPending() && <div class="loading">Filtering...</div>}
        <p>Found {filteredItems().length} items</p>
      </div>

      <div class="items">
        <For each={filteredItems()}>
          {(item) => (
            <div class="item">
              <h3>{item.name}</h3>
              <p>{item.description}</p>
            </div>
          )}
        </For>
      </div>
    </div>
  );
}

function generateItems(count: number) {
  return Array.from({ length: count }, (_, i) => ({
    id: i,
    name: `Item ${i}`,
    description: `Description for item ${i}`
  }));
}

Global State Management with Context

Create global state management using context and reactive primitives.

Usage Examples:

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

// Global store interface
interface Store {
  user: User | null;
  cart: CartItem[];
  theme: Theme;
}

interface StoreActions {
  setUser: (user: User | null) => void;
  addToCart: (item: Product) => void;
  removeFromCart: (itemId: number) => void;
  setTheme: (theme: Theme) => void;
}

// Create store context
const StoreContext = createContext<Store & StoreActions>();

// Store provider
function StoreProvider(props: { children: JSX.Element }) {
  const [user, setUser] = createSignal<User | null>(null);
  const [cart, setCart] = createSignal<CartItem[]>([]);
  const [theme, setTheme] = createSignal<Theme>({ mode: "light" });

  const addToCart = (product: Product) => {
    setCart(prev => {
      const existing = prev.find(item => item.product.id === product.id);
      if (existing) {
        return prev.map(item =>
          item.product.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      }
      return [...prev, { product, quantity: 1 }];
    });
  };

  const removeFromCart = (productId: number) => {
    setCart(prev => prev.filter(item => item.product.id !== productId));
  };

  const store = createMemo(() => ({
    user: user(),
    cart: cart(),
    theme: theme(),
    setUser,
    addToCart,
    removeFromCart,
    setTheme
  }));

  return (
    <StoreContext.Provider value={store()}>
      {props.children}
    </StoreContext.Provider>
  );
}

// Custom hook to use store
function useStore() {
  const store = useContext(StoreContext);
  if (!store) {
    throw new Error("useStore must be used within StoreProvider");
  }
  return store;
}

// Components using the global store
function ShoppingCart() {
  const { cart, removeFromCart } = useStore();
  
  const total = createMemo(() => 
    cart.reduce((sum, item) => sum + item.product.price * item.quantity, 0)
  );

  return (
    <div>
      <h2>Shopping Cart</h2>
      <For each={cart} fallback={<p>Cart is empty</p>}>
        {(item) => (
          <div class="cart-item">
            <span>{item.product.name} x {item.quantity}</span>
            <span>${(item.product.price * item.quantity).toFixed(2)}</span>
            <button onClick={() => removeFromCart(item.product.id)}>
              Remove
            </button>
          </div>
        )}
      </For>
      <div class="total">Total: ${total().toFixed(2)}</div>
    </div>
  );
}

function ProductList() {
  const { addToCart } = useStore();
  const [products] = createResource(() => fetchProducts());

  return (
    <div>
      <h2>Products</h2>
      <For each={products()}>
        {(product) => (
          <div class="product">
            <h3>{product.name}</h3>
            <p>${product.price}</p>
            <button onClick={() => addToCart(product)}>
              Add to Cart
            </button>
          </div>
        )}
      </For>
    </div>
  );
}

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