Anti examples React Click anywhere to arm sound
example / react

Two hooks, and the bug they prevent.

The engine needs no React binding: import it and call it. What React changes is who stops a sound, because a component can vanish mid-gesture.

01

A drag that cleans up after itself

Drag the fader and, still holding it, press a view button. Done naively, the drone outlives its component and hums until reload. Here the effect's cleanup stops it, and the view change calls Anti.silence() on the way out.

// useDrone.js
import { useEffect, useRef } from 'react'
import Anti from 'anti-tapes'

export function useDrone(id) {
  const held = useRef(false)

  // The whole reason this is a hook. A component can
  // unmount mid-gesture (a route change, a filtered
  // list, a modal closing over it) and the pointerup
  // that would have ended the drag never arrives at
  // anything that still exists.
  useEffect(() => () => {
    if (held.current) Anti.drone.stop(id)
  }, [id])

  return {
    start(v) { held.current = true; Anti.drone.start(id, v) },
    move(v)  { if (held.current) Anti.drone.move(id, v) },
    stop()   { if (!held.current) return
               held.current = false; Anti.drone.stop(id) }
  }
}
02

What the engine handles, and what it cannot

The engine is a module with its own state, so it answers most integration questions. Two are yours.

Import in a server rendersafe
Imported twice, two componentsone engine
StrictMode double effectssafe
Unmount mid-gestureyours
Leaving a view mid-gestureyours

Drone and progress runs are keyed by an id you choose, so starting one twice moves the existing run instead of stacking a voice. A double-invoked effect is harmless.

// The route-change contract. One call, and nothing
// that was held by the view being left is still
// ringing over the view being entered.

useEffect(() => {
  return () => Anti.silence()
}, [route])

// Importing on the server is silent, not fatal: the
// engine arms itself on the first user gesture and
// no-ops before there is one, so there is nothing to
// guard and no dynamic import to arrange.
import Anti from 'anti-tapes'