# Use Static Effect Event

> useStaticEffectEvent hook in Supabase Studio — a userland polyfill for Use when this capability is needed.

- Skill: `tomevault-io/use-static-effect-event` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/use-static-effect-event`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/use-static-effect-event/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/use-static-effect-event

---


# useStaticEffectEvent

Located at `apps/studio/hooks/useStaticEffectEvent.ts`.

A userland polyfill for React's `useEffectEvent` (stable in React 19.2). It solves the stale closure problem: gives you a **stable callback** that always reads the latest props/state without those values triggering Effect re-runs.

## The Problem It Solves

Without it, you face two bad options inside `useEffect`:

1. **Add values to dependencies** → unnecessary Effect re-runs (teardown/reconnect)
2. **Omit from dependencies** → stale closure bugs (outdated values)

```tsx
// Problem: re-runs every time `theme` changes, even though we only
// want to reconnect when `roomId` changes
useEffect(() => {
  const connection = createConnection(roomId)
  connection.on('connected', () => {
    showNotification('Connected!', theme) // theme causes unwanted reconnects
  })
  return () => connection.disconnect()
}, [roomId, theme])
```

## When to Use

1. Read latest state/props inside an Effect without re-triggering it
2. Create stable callbacks that always use current values
3. Avoid stale closures in event handlers used within Effects

### Pattern 1: Sync data without re-running on every change

```tsx
const syncApiPrivileges = useStaticEffectEvent(() => {
  if (hasLoadedInitialData.current) return
  if (!apiAccessStatus.isSuccess) return
  if (!privilegesForTable) return

  hasLoadedInitialData.current = true
  setPrivileges(privilegesForTable.privileges)
})

useEffect(() => {
  syncApiPrivileges()
}, [apiAccessStatus.status, syncApiPrivileges])
```

### Pattern 2: Stable callbacks for async operations

```tsx
const exportInternal = useStaticEffectEvent(
  async ({ bypassConfirmation }: { bypassConfirmation: boolean }) => {
    if (!params.enabled) return
    const { projectRef, connectionString, entity, totalRows } = params
    // complex async logic using latest params
  }
)

// Stable reference — safe to use in useCallback
const exportInDesiredFormat = useCallback(
  () => exportInternal({ bypassConfirmation: false }),
  [exportInternal]
)
```

### Pattern 3: Infinite scroll / pagination triggers

```tsx
const fetchNext = useStaticEffectEvent(() => {
  if (lastItem && lastItem.index >= items.length - 1 && hasNextPage && !isFetchingNextPage) {
    fetchNextPage()
  }
})

useEffect(fetchNext, [lastItem, fetchNext])
```

## When NOT to Use

**Don't use it to hide legitimate dependencies:**

```tsx
// ❌ Bad — roomId IS a legitimate dependency; this hides a bug
const connect = useStaticEffectEvent(() => {
  const connection = createConnection(roomId)
  connection.connect()
})
useEffect(() => {
  connect()
}, [connect]) // Won't reconnect when roomId changes!

// ✅ roomId belongs in deps
useEffect(() => {
  const connection = createConnection(roomId)
  connection.connect()
  return () => connection.disconnect()
}, [roomId])
```

**Don't use it for simple event handlers outside Effects:**

```tsx
// ❌ Unnecessary — not used inside an Effect
const handleClick = useStaticEffectEvent(() => console.log(count))

// ✅ Regular function is fine
const handleClick = () => console.log(count)
```

## Rules

1. Only call the returned function **inside Effects** (`useEffect`, `useLayoutEffect`)
2. Don't pass it to other components or hooks as a callback prop
3. Use for **non-reactive logic only** — reads values but shouldn't trigger re-runs
4. **Include it in dependency arrays** when used in `useEffect` (it's stable, won't cause re-runs)

## How It Works

```tsx
export const useStaticEffectEvent = <Callback extends Function>(callback: Callback) => {
  const callbackRef = useRef(callback)

  useLayoutEffect(() => {
    callbackRef.current = callback // always latest
  })

  const eventFn = useCallback((...args: any) => {
    return callbackRef.current(...args)
  }, []) // stable reference

  return eventFn as unknown as Callback
}
```

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/supabase) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-16 -->

