Skip to content

Conversation

@rodrigofariow
Copy link
Contributor

@rodrigofariow rodrigofariow commented Nov 5, 2025

Hi there 👋
For a library like this one that relies heavily on correct and stable rendering of useEffect and useMemo dependencies, I suggest we add a useEffectEvent ponyfill, to fill that gap.
This ponyfill is proven to work and the new version of eslint react-hooks plugin, that is already installed, supports verifying and using these events.

I took the freedom to add it in key places like useMapsEventListener and useDomEventListener.

} = props;
const positionLng = positionProp?.lng;
const positionLat = positionProp?.lat;
const position = useMemo(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why I added this was so that devs can still send inline positions to AdvancedMarker, e.g:

return <AdvancedMarker position={{ lat:1, lng:1}}>

And we make sure to not set the marker to this position again (which can trigger potentially heavy calculations on google maps API side. At least to my knowledge they don't do a deep comparison to check if the previously assigned position is deeply different from the new one.

* @public
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useEffectEvent<const T extends (...args: any[]) => void>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is based on https://github.com/sanity-io/use-effect-event/blob/main/src/useEffectEvent.ts and on a ponyfill that I use at my company.
But for transparency, I essentially copied the one from https://github.com/sanity-io/use-effect-event/blob/main/src/useEffectEvent.ts, which is currently widely used, and removed their extra check that uses some weird React.use thing to make sure devs don't use this useEffectEvent in render. Reasons I didn't add that part:

  • It's simpler this way
  • Eslint react plugin takes care of identifying if we only use it for useEffect events or if we use it somewhere else.

* Internally used to bind events to DOM nodes.
* @internal
*/
export function useDomEventListener<T extends (...args: any[]) => void>(
Copy link
Contributor Author

@rodrigofariow rodrigofariow Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For both useDomEventListener and useMapsEventListener, to prevent adding and removing listeners on every render (which was the previous logic), I verify if the callback is defined and I useEffectEvent as the callback. eslint makes sure callbackEvent is not set on useEffect dependencies.
The tricky part of useEffectEvent, both the ponyfill and the "native" implementation, is that the returned function is a new one every time. As such, we should not directly assign callbackEvent to the addEventListener or addListener functions as we risk sending a different function on removeEventListener than the one on addEventListener, which could theoretically cause a memory leak.
I believe this is per design as you can see on https://github.com/facebook/react/blob/b7e2de632b2a160bc09edda1fbb9b8f85a6914e8/packages/react-reconciler/src/ReactFiberHooks.js#L2728

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants