Shadcn Hooks

useEffectEvent

A hook to declare an effect event

useEffectEvent

npx shadcn@latest add "https://shadcn-hooks.vercel.app/r/use-effect-event.json"
pnpm dlx shadcn@latest add "https://shadcn-hooks.vercel.app/r/use-effect-event.json"
yarn dlx shadcn@latest add "https://shadcn-hooks.vercel.app/r/use-effect-event.json"
bun x shadcn@latest add "https://shadcn-hooks.vercel.app/r/use-effect-event.json"

Copy and paste the following code into your project.

use-effect-event.ts
import * as React from 'react'

const context = React.createContext(true)

function forbiddenInRender() {
  throw new Error(
    "A function wrapped in useEffectEvent can't be called during rendering.",
  )
}

// We can only check if we're in a render phase, beyond initial render, in React 19, with its `React.use` hook.
const isInvalidExecutionContextForEventFunction =
  'use' in React
    ? () => {
        // There's no way to check if we're in a render phase from outside of React, the API used by useEffectEvent is private: https://github.com/facebook/react/blob/a00ca6f6b51e46a0ccec54a2231bfe7a1ed9ae1d/packages/react-reconciler/src/ReactFiberWorkLoop.js#L1785-L1788
        // So to emulate the same behavior, we call the use hook and if it doesn't throw, we're in a render phase.
        try {
          return React.use(context)
        } catch {
          return false
        }
      }
    : () => false

/**
 * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19.
 * https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event
 * To learn more about the ponyfill itself, see: https://blog.bitsrc.io/a-look-inside-the-useevent-polyfill-from-the-new-react-docs-d1c4739e8072
 * @public
 */
export function useEffectEvent<const T extends (...args: any[]) => void>(
  fn: T,
): T {
  /**
   * For both React 18 and 19 we set the ref to the forbiddenInRender function, to catch illegal calls to the function during render.
   * Once the insertion effect runs, we set the ref to the actual function.
   */
  const ref = React.useRef(forbiddenInRender as T)

  React.useInsertionEffect(() => {
    ref.current = fn
  }, [fn])

  return ((...args: any) => {
    // Performs a similar check to what React does for `useEffectEvent`:
    // 1. https://github.com/facebook/react/blob/b7e2de632b2a160bc09edda1fbb9b8f85a6914e8/packages/react-reconciler/src/ReactFiberHooks.js#L2729-L2733
    // 2. https://github.com/facebook/react/blob/b7e2de632b2a160bc09edda1fbb9b8f85a6914e8/packages/react-reconciler/src/ReactFiberHooks.js#L2746C9-L2750
    if (isInvalidExecutionContextForEventFunction()) {
      forbiddenInRender()
    }

    const latestFn = ref.current!
    return latestFn(...args)
  }) as T
}

credits

Last updated on