Files
skills/docs/ui/react/react-audio/src/hooks/use-timeline/use-timeline.md
2026-05-29 08:20:56 +01:00

2.4 KiB

hooks/use-timeline

(no submodules)

Purpose

useTimeline() returns the provider's single TimelineEngine instance, the canonical owner of timeline state — tracks, clips, selection, loop region, master volume, undo/redo. The engine exposes its own typed event surface (engine.on(event, listener)); consumers who want to subscribe to engine events should pair this hook with useSyncExternalStore and pass engine.on(...) as the subscribe callback. The hook itself returns the engine handle and does not trigger re-renders on engine state changes.

Features

  • Returns the TimelineEngine instance owned by the provider.
  • Stable identity across re-renders within a ready phase.
  • Throws REACT_AUDIO.NO_PROVIDER outside <AudioProvider>.
  • Throws REACT_AUDIO.PROVIDER_PENDING during async init.

Usage: Basic

import { useTimeline } from "@suaveplan/react-audio";

function AddTrackButton() {
    const timeline = useTimeline();
    return (
        <button type="button" onClick={() => timeline.addTrack({ name: "New Track" })}>
            Add track
        </button>
    );
}

Usage: Advanced

import { useSyncExternalStore } from "react";
import { useTimeline } from "@suaveplan/react-audio";

function useTrackCount(): number {
    const timeline = useTimeline();
    return useSyncExternalStore(
        (listener) => timeline.on("trackChange", listener as never),
        () => timeline.getState().tracks.length,
        () => timeline.getState().tracks.length
    );
}

API Reference

  • useTimeline(): TimelineEngine — see @suaveplan/audio-timeline for the full engine surface.

Implementation Notes

The hook deliberately stops short of re-rendering on every engine event. The TimelineEngine has a rich change surface and re-rendering on every state delta would be expensive at chain scale. Consumers choose which events they care about and pair the engine handle with useSyncExternalStore for narrow subscriptions, paying only for the re-renders they want. Mutator methods (addTrack, updateTrack, removeTrack, setTempo, setMasterVolume, …) are exposed directly on the engine handle — callers wrap them in their own callbacks when they need referential stability across renders. For per-track state prefer useTrack(id), which already wraps useSyncExternalStore against the timeline's trackChange event so its subscription is narrow by construction.