ZeeCoder / use-resize-observer

A React hook that allows you to use a ResizeObserver to measure an element's size.
MIT License
644 stars 42 forks source link

Question regarding environments missing ResizeObserver #97

Closed martinstark closed 1 month ago

martinstark commented 1 year ago

I have a library where I don't necessarily want to polyfill ResizeObserver if a device is missing support, meaning I'd like to avoid calling useResizeObserver when ResizeObserver does not exist in DOM.

Due to React semantics, I can't make the call to the hook conditional.

Would it be reasonable to extend the hook with an option to disable resize detection when it isn't supported (e.g. by blocking calls to new ResizeObserver internally)?

I know that I can add the hook to a conditionally rendered element in order to prevent it from running if ResizeObserver isn't in DOM, e.g.:

  { ("ResizeObserver" in window) && <SomeComponentWithResizeObserver callback={setSomeStateOnResize} /> }

However, if my library only exports a hook and no component, that wouldn't be possible.

I can create a PR if this sounds reasonable.

ZeeCoder commented 1 year ago

I think you might be able to achieve what you want with the current hook. When you don't pass in an element to to hook to be observed, then the hook doesn't create an RO instance, and therefore won't need a polyfill either up until that point.

This lazy-initialization was introduced for SSR compatibility initially, but could be used for this as follows:

import useResizeObserverRaw = 'use-resize-observer';

const isRoAvailable = typeof window !== 'undefined' && ("ResizeObserver" in window);

export const useResizeObserver = () => {
  const { ref: refRaw, with, height } = useResizeObserverRaw();

  const ref = useCallback((element) => {
    if (isRoAvailable) { refRaw(element); }
  }, [refRaw, isRoAvailable]);

  return useMemo(() => ({ref, width, height}), [ref, width, height])
}

Then you'd use this hook as usual:

const { ref, width = 42, height = 24 } = useResizeObserver();

:point_up: Where the default values would be used when no RO is available.

I'm a bit unsure how this would work with SSR, but I think when hydration happens it would call the ref passed in with the new isRoAvailable variable available to it. Of course you can do other things as well depending on what you need in the if (isRoAvailable) check, not just skipping hook initialisation.

Hope this helps.

martinstark commented 1 year ago

Thanks @ZeeCoder, that's great to know. I should have looked more closely at the source. I don't expect this to be a very common usecase, but could be useful to have this behaviour documented :+1:

ZeeCoder commented 1 year ago

Great, I'll leave this issue open until it's documented, I think it's an interesting use-case. @martinstark can you just also confirm when you did the implementation if there were anything else that needed to be considered, or if it was just copy-c of the above code? Just so I'd know what to document.

martinstark commented 1 year ago

I'm using the inverted version where I'm passing a ref into the hook, seems to work in an even easier manner:

  delete window.ResizeObserver;

  // ...

  useResizeObserver({
    ref: null,
    onResize: () => {
      // ...
    },
  });

Triggers no error.

ZeeCoder commented 1 year ago

Thanks for sharing! That's not something I can recommend in the docs with the delete and all though, so I'll probably just use my original recommendation.

It seems like the idea behind it is already enough to nudge people in the right direction anyway. :+1:

martinstark commented 1 year ago

I just used delete in order to quickly test what happens in an environment where ResizeObserver is missing and calling it would throw an error. Setting ref to null prevents calls to ResizeObserver. That delete is not part of my actual code :grimacing:

Real code example would be something like:

  useResizeObserver({
    // prevent crashing in environments that do not support ResizeObserver
    ref: isRoAvailable ? someRef : null,
    onResize: () => {
      // ...
    },
  })