There's a recurring tension in React: you want a callback with a stable identity (so it doesn't break React.memo children or re-trigger effects), but you also want it to see the latest props and state (not a value frozen when the callback was created). useCallback gives you one or the other — an empty dep array is stable but goes stale; listing deps stays fresh but changes identity. useEventCallback gives you both, and it's the pattern behind React's proposed useEvent.
Implement useEventCallback(fn). Return a function whose identity never changes across renders, but that always invokes the most recent fn — forwarding arguments, return value, and this.
function useEventCallback(fn) {
// returns a stable function that always calls the latest fn
}
function Chat({ roomId }) {
const onSend = useEventCallback((msg) => sendTo(roomId, msg));
// onSend has ONE identity forever, but always uses the current roomId
useEffect(() => socket.on('ready', onSend), [onSend]); // effect never re-runs
}
const [count, setCount] = useState(0);
const log = useEventCallback(() => console.log(count)); // always the latest count
fn in a ref and update it each render; the stable wrapper reads the ref when called.this (use apply).useEvent, the ref updates in a layout effect, so calling the wrapper mid-render could read a stale fn. Use it in handlers and effects.