mcpField
U
use-static-effect-event

use-static-effect-event

github.com/supabase開発114.3k89 回インストール今日更新
リポジトリ

use-static-effect-event について

useStaticEffectEvent は React の useEffectEvent のユーザーランド実装で、Effect を再実行せずに最新の props と state を読み取る安定したコールバックを提供します。Effect 内の非リアクティブな処理向けで、古いクロージャの問題を避けます。

主な機能

  1. 01最新の state と props を読む安定したコールバック
  2. 02古いクロージャの回避
  3. 03不要な Effect の再実行を防止
  4. 04非同期処理やページネーションのトリガーに対応

活用シーン

  1. 01Effect を再実行せず最新値を読み取る
  2. 02非同期処理用の安定したコールバックを作成
  3. 03無限スクロールで次ページの読み込みを開始

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/supabase/supabase --skill use-static-effect-event

インストール後、Claude Code で/use-static-effect-eventを入力して読み込まれたか確認します。

詳細

useStaticEffectEvent

apps/studio/hooks/useStaticEffectEvent.ts にあります。

React の useEffectEvent(React 19.2 で安定版)をユーザーランドでポリフィルしたものです。古いクロージャの問題を解決します。値の変化によって Effect が再実行されることなく、最新の props/state を常に読み取る安定したコールバックを提供します。

解決する問題

これを使わない場合、useEffect 内では次のどちらかを選ぶ必要があり、どちらにも問題があります。

  1. 値を依存配列に追加する → 不要な Effect の再実行(クリーンアップ/再接続)
  2. 依存配列から省く → 古いクロージャによるバグ(値が更新されない)
// 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])

使用する場面

  1. Effect を再実行せずに、Effect 内で最新の state/props を読み取る
  2. 現在の値を常に使う安定したコールバックを作成する
  3. Effect 内で使用するイベントハンドラーで、古いクロージャによる問題を回避する
パターン 1:変更のたびに再実行せずにデータを同期する
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])
パターン 2:非同期処理用の安定したコールバック
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]
)
パターン 3:無限スクロール/ページネーションのトリガー
const fetchNext = useStaticEffectEvent(() => {
  if (lastItem && lastItem.index >= items.length - 1 && hasNextPage && !isFetchingNextPage) {
    fetchNextPage()
  }
})

useEffect(fetchNext, [lastItem, fetchNext])

使用しない場面

正当な依存関係を隠すために使わないでください。

// ❌ 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])

Effect の外で使う単純なイベントハンドラーには使用しないでください。

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

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

ルール

  1. 返された関数は Effect 内(useEffect、useLayoutEffect)でのみ呼び出してください。
  2. コールバックプロパティとして、ほかのコンポーネントやフックに渡さないでください。
  3. 非リアクティブなロジックにのみ使用してください。値を読み取りますが、再実行のトリガーにはなりません。
  4. useEffect で使用する場合は、依存配列に含めてください(安定しているため、再実行は発生しません)。

仕組み

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
}

コメント