or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

edge-cases.mddocs/examples/

Edge Cases and Advanced Scenarios

Advanced patterns, edge cases, and solutions for complex Svelte scenarios.

Reactivity Edge Cases

Preventing Infinite Loops

<script>
  let count = $state(0);
  
  // ❌ This creates an infinite loop
  // $effect(() => {
  //   count++;
  // });
  
  // ✅ Use $effect.pre to read before write
  $effect.pre(() => {
    if (count < 10) {
      count++;
    }
  });
</script>

Conditional Reactivity

<script>
  let enabled = $state(true);
  let data = $state(null);
  
  // Only compute when enabled
  let computed = $derived.by(() => {
    if (!enabled) return null;
    return data ? data.value * 2 : 0;
  });
</script>

Shallow vs Deep Reactivity

<script>
  // Deep reactivity - nested changes trigger updates
  let deep = $state({ nested: { value: 0 } });
  
  // Shallow reactivity - only top-level changes trigger updates
  let shallow = $state.raw({ nested: { value: 0 } });
  
  function updateDeep() {
    deep.nested.value++; // ✅ Triggers reactivity
  }
  
  function updateShallow() {
    shallow.nested.value++; // ❌ Does NOT trigger reactivity
    shallow = { ...shallow, nested: { ...shallow.nested, value: shallow.nested.value + 1 } }; // ✅ Triggers reactivity
  }
</script>

Snapshot for External Libraries

<script>
  let state = $state({ count: 0, items: [1, 2, 3] });
  
  function sendToExternalLibrary() {
    // External library shouldn't trigger reactivity
    const snapshot = $state.snapshot(state);
    externalLibrary.process(snapshot);
  }
  
  function compareStates() {
    const before = $state.snapshot(state);
    state.count++;
    const after = $state.snapshot(state);
    console.log('Changed:', before.count !== after.count);
  }
</script>

Component Lifecycle Edge Cases

Async Cleanup

<script>
  import { onMount } from 'svelte';
  
  onMount(async () => {
    const data = await fetch('/api/data').then(r => r.json());
    
    // ⚠️ Cleanup from async onMount is ignored
    // Use onDestroy for cleanup instead
    return () => {
      // This won't run
    };
  });
  
  import { onDestroy } from 'svelte';
  
  let subscription;
  
  onMount(async () => {
    subscription = await setupSubscription();
  });
  
  onDestroy(() => {
    if (subscription) {
      subscription.cleanup();
    }
  });
</script>

Multiple Mounts

<script>
  import { mount, unmount } from 'svelte';
  import Component from './Component.svelte';
  
  let instances = [];
  
  function addComponent() {
    const instance = mount(Component, {
      target: document.body,
      props: { id: instances.length }
    });
    instances.push(instance);
  }
  
  function removeComponent() {
    if (instances.length > 0) {
      const instance = instances.pop();
      unmount(instance);
    }
  }
</script>

Hydration Mismatch Recovery

import { hydrate } from 'svelte';
import App from './App.svelte';

// Attempt to recover from hydration mismatches
const app = hydrate(App, {
  target: document.getElementById('app'),
  props: { data: serverData },
  recover: true // Enables recovery mode
});

Store Edge Cases

Store Subscription Cleanup

import { writable } from 'svelte/store';

const store = writable(0, (set) => {
  const interval = setInterval(() => {
    set(Math.random());
  }, 1000);
  
  // Cleanup when last subscriber unsubscribes
  return () => {
    clearInterval(interval);
  };
});

Derived Store with Initial Value

import { derived } from 'svelte/store';
import { asyncData } from './stores.js';

// Show loading state until first value arrives
const processed = derived(
  asyncData,
  $data => $data ? process($data) : null,
  'Loading...' // Initial value shown until first derivation
);

Async Derived Store with Cancellation

import { derived } from 'svelte/store';
import { searchQuery } from './stores.js';

const searchResults = derived(
  searchQuery,
  ($query, set) => {
    if (!$query) {
      set([]);
      return;
    }
    
    const controller = new AbortController();
    
    fetch(`/api/search?q=${$query}`, {
      signal: controller.signal
    })
      .then(r => r.json())
      .then(set)
      .catch(() => set([]));
    
    // Cancel previous request when query changes
    return () => controller.abort();
  },
  []
);

Context Edge Cases

Context Inheritance

<!-- Parent.svelte -->
<script>
  import { setContext } from 'svelte';
  
  setContext('theme', 'dark');
</script>

<slot />

<!-- Child.svelte -->
<script>
  import { getContext } from 'svelte';
  
  // Gets 'dark' from parent
  const theme = getContext('theme');
</script>

<!-- Nested Child.svelte -->
<script>
  import { getContext, setContext } from 'svelte';
  
  // Override parent context
  const parentTheme = getContext('theme');
  setContext('theme', 'light'); // Children will see 'light'
</script>

Context with Default Values

<script>
  import { getContext } from 'svelte';
  
  // Provide default if context doesn't exist
  const theme = getContext('theme') ?? 'light';
  const config = getContext('config') ?? { enabled: true };
</script>

Transition Edge Cases

Conditional Transitions

<script>
  import { fade, fly } from 'svelte/transition';
  
  let show = $state(true);
  let useFly = $state(false);
</script>

{#if show}
  <div transition={useFly ? fly : fade}>
    Content
  </div>
{/if}

Transition with Dynamic Parameters

<script>
  import { fly } from 'svelte/transition';
  
  let show = $state(true);
  let direction = $state('down');
  
  let params = $derived({
    y: direction === 'down' ? 50 : -50,
    duration: 300
  });
</script>

{#if show}
  <div transition:fly={params}>
    Content
  </div>
{/if}

Transition on Keyed Each Blocks

<script>
  import { fly } from 'svelte/transition';
  
  let items = $state([
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' }
  ]);
</script>

{#each items as item (item.id)}
  <div transition:fly={{ y: 50 }}>
    {item.name}
  </div>
{/each}

Server-Side Rendering Edge Cases

CSP Headers

import { render } from 'svelte/server';
import App from './App.svelte';

const { head, body } = render(App, {
  props: { data },
  csp: {
    nonce: 'random-nonce-value'
  }
});

Context in SSR

import { render } from 'svelte/server';
import App from './App.svelte';

const context = new Map();
context.set('user', req.user);
context.set('theme', 'dark');

const { head, body } = render(App, {
  props: { data },
  context
});

Compiler Edge Cases

Dynamic Component Compilation

import { compile } from 'svelte/compiler';

const source = `
  <script>
    let count = $state(0);
  </script>
  <button onclick={() => count++}>
    {count}
  </button>
`;

const result = compile(source, {
  filename: 'Dynamic.svelte',
  generate: 'client',
  dev: false
});

// Evaluate compiled code
const Component = new Function(result.js.code)();

Preprocessing Edge Cases

import { preprocess } from 'svelte/compiler';
import sass from 'sass';

const processed = await preprocess(
  source,
  [
    {
      style: async ({ content, attributes }) => {
        if (attributes.lang === 'scss') {
          const result = sass.compileString(content);
          return { code: result.css };
        }
      }
    }
  ],
  { filename: 'Component.svelte' }
);

TypeScript Edge Cases

Generic Component Props

<script lang="ts">
  interface Props<T> {
    items: T[];
    render: (item: T) => string;
  }
  
  let { items, render }: Props<any> = $props();
</script>

{#each items as item}
  <div>{render(item)}</div>
{/each}

Event Handler Types

<script lang="ts">
  import type { EventHandler } from 'svelte/elements';
  
  let handleClick: EventHandler<MouseEvent, HTMLButtonElement> = (e) => {
    console.log(e.currentTarget); // Typed as HTMLButtonElement
  };
</script>

<button onclick={handleClick}>Click</button>

Performance Edge Cases

Avoiding Unnecessary Re-renders

<script>
  let items = $state.raw([...]); // Use raw for large arrays
  
  // Only update when necessary
  let filtered = $derived.by(() => {
    // Expensive computation
    return items.filter(/* ... */);
  });
</script>

Lazy Loading Components

<script>
  import { onMount } from 'svelte';
  
  let Component = $state(null);
  let loading = $state(true);
  
  onMount(async () => {
    const module = await import('./HeavyComponent.svelte');
    Component = module.default;
    loading = false;
  });
</script>

{#if loading}
  <p>Loading...</p>
{:else if Component}
  <svelte:component this={Component} />
{/if}