Skip to content

ResizeObserver loop completed with undelivered notifications #103

Closed
@tommedema

Description

@tommedema

Describe the bug
We keep seeing thousands of these logs in our sentry bug tracker. It occurs in the window.onerror event listener:

ResizeObserver loop completed with undelivered notifications

Frameworks/Libraries used
React 16.9.0

I'm using it in a hook useResizeObserver.ts:

/**
 * Inspired by ZeeCoder/use-resize-observer (MIT licensed).
 * @see https://github.com/ZeeCoder/use-resize-observer
 * @see https://github.com/juggle/resize-observer
 */

import { useEffect, useState, useRef, useMemo, RefObject } from 'react'
import { ResizeObserver } from '@juggle/resize-observer'

interface IUseResizeObserverProps<T> {
  ref?: ((instance: T | null) => void) | RefObject<HTMLDivElement> | null | undefined,
  onResize?: (size: { width: number, height: number }) => void
}

export interface ISize {
  width?: number,
  height?: number
}

const useResizeObserver = <T>({ ref, onResize }: IUseResizeObserverProps<T> = {}) => {
  // `defaultRef` Has to be non-conditionally declared here whether or not it'll
  // be used as that's how hooks work.
  // @see https://reactjs.org/docs/hooks-rules.html#explanation
  const defaultRef = useRef(null)
  ref = ref || defaultRef

  const [size, setSize] = useState<ISize>({
    width: undefined,
    height: undefined
  })

  // Using a ref to track the previous width / height to avoid unnecessary renders
  const previous = useRef<ISize>({
    width: undefined,
    height: undefined
  })

  useEffect(() => {
    if (
      typeof ref !== 'object' ||
      ref === null ||
      !(ref.current instanceof Element)
    ) {
      return
    }

    const element = ref.current

    const resizeObserver = new ResizeObserver(entries => {
      if (!Array.isArray(entries)) {
        return
      }

      // Since we only observe the one element, we don't need to loop over the array
      if (!entries.length) {
        return
      }

      const entry = entries[0]

      // `Math.round` is in line with how CSS resolves sub-pixel values
      const newWidth = Math.round(entry.contentRect.width)
      const newHeight = Math.round(entry.contentRect.height)

      if (
        previous.current.width !== newWidth ||
        previous.current.height !== newHeight
      ) {
        const newSize = { width: newWidth, height: newHeight }
        if (onResize) {
          onResize(newSize)
        } else {
          previous.current.width = newWidth
          previous.current.height = newHeight

          setSize(newSize)
        }
      }
    })

    resizeObserver.observe(element)

    return () => {
      resizeObserver.unobserve(element)
      resizeObserver.disconnect()
    }
  }, [ref, onResize])

  return useMemo(() => ({ ref, ...size }), [ref, size])
}

export default useResizeObserver

Smartphone (please complete the following information):
E.g. one case

  • Device: iPhone
  • OS: iOS 13.4
  • Browser: Chrome Mobile iOS
  • Version: 80.0.3987

(But we also see this on Android, Windows, Linux, iOS safari)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions