← Files WixARCHIVED FILE

skills/wix-headless-templates/portfolio/project/src/wix/portfolio/collections-store.ts

2.13 KB · Oct 8, 2026 · 12:02 UTC

↓ Download file

See the change to this file →

// The collections gallery as a framework-free store — the logic behind useCollections, usable
// from React (useCollections wraps it with useSyncExternalStore), from a static page or
// Vue/Svelte (subscribe and render), or as the specification for a port. State, subscribe/getState,
// start()/stop(), emit after every change.
//
// SSR-friendly: seed with `initialCollections` and nothing is fetched; without a seed `start()`
// fetches once. One store per mounted gallery: createCollectionsStore(), not a singleton.
import { fetchCollections } from "./portfolio";
import type { CollectionSummary } from "./types";

export interface CollectionsStoreOptions {
  initialCollections?: CollectionSummary[];
}

export interface CollectionsState {
  /** null while the first load is in flight — render skeletons, not an empty state. */
  collections: CollectionSummary[] | null;
  error: string | null;
}

export interface CollectionsStore {
  getState(): CollectionsState;
  subscribe(listener: () => void): () => void;
  /** Fetch when no seed was given. Call once when mounted (a browser). */
  start(): void;
  /** Stop reacting; drop a late response. */
  stop(): void;
}

export function createCollectionsStore({ initialCollections }: CollectionsStoreOptions = {}): CollectionsStore {
  let collections: CollectionSummary[] | null = initialCollections ?? null;
  let error: string | null = null;
  let started = false;
  const listeners = new Set<() => void>();
  let snapshot: CollectionsState | null = null;
  const emit = () => { snapshot = null; for (const fn of listeners) fn(); };

  return {
    getState() {
      return (snapshot ??= { collections, error });
    },
    subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
    start() {
      if (started) return;
      started = true;
      if (initialCollections) return;
      fetchCollections()
        .then((c) => { if (started) { collections = c; emit(); } })
        .catch((e) => {
          if (!started) return;
          collections = [];
          error = e instanceof Error ? e.message : String(e);
          emit();
        });
    },
    stop() { started = false; },
  };
}

SHA-256: 79eee39239e62c781ae0817ab70ffd2ab9f8b69237b1d01bd50130ac4612c090