Giter VIP home page Giter VIP logo

use-resize-observer's Introduction

use-resize-observer


useResizeObserver

A React hook that allows you to use a ResizeObserver to measure an element's size.

npm version build BrowserStack Status

Highlights

  • Written in TypeScript.
  • Tiny: 648B (minified, gzipped) Monitored by size-limit.
  • Exposes an onResize callback if you need more control.
  • box option.
  • Works with SSR.
  • Works with CSS-in-JS.
  • Supports custom refs in case you had one already.
  • Uses RefCallback by default To address delayed mounts and changing ref elements.
  • Ships a polyfilled version
  • Handles many edge cases you might not even think of. (See this documentation and the test cases.)
  • Easy to compose (Throttle / Debounce, Breakpoints)
  • Tested in real browsers (Currently latest Chrome, Firefox, Edge, Safari, Opera, IE 11, iOS and Android, sponsored by BrowserStack)

In Action

CodeSandbox Demo

Install

yarn add use-resize-observer --dev
# or
npm install use-resize-observer --save-dev

Options

Option Type Description Default
ref undefined | RefObject | HTMLElement A ref or element to observe. undefined
box undefined | "border-box" | "content-box" | "device-pixel-content-box" The box model to use for observation. "content-box"
onResize undefined | ({ width?: number, height?: number }) => void A callback receiving the element size. If given, then the hook will not return the size, and instead will call this callback. undefined
round undefined | (n: number) => number A function to use for rounding values instead of the default. Math.round()

Response

Name Type Description
ref RefCallback A callback to be passed to React's "ref" prop.
width undefined | number The width (or "inlineSize") of the element.
height undefined | number The height (or "blockSize") of the element.

Basic Usage

Note that the default builds are not polyfilled! For instructions and alternatives, see the Transpilation / Polyfilling section.

import React from "react";
import useResizeObserver from "use-resize-observer";

const App = () => {
  const { ref, width = 1, height = 1 } = useResizeObserver<HTMLDivElement>();

  return (
    <div ref={ref}>
      Size: {width}x{height}
    </div>
  );
};

To observe a different box size other than content box, pass in the box option, like so:

const { ref, width, height } = useResizeObserver<HTMLDivElement>({
  box: "border-box",
});

Note that if the browser does not support the given box type, then the hook won't report any sizes either.

Box Options

Note that box options are experimental, and as such are not supported by all browsers that implemented ResizeObservers. (See here.)

content-box (default)

Safe to use by all browsers that implemented ResizeObservers. The hook internally will fall back to contentRect from the old spec in case contentBoxSize is not available.

border-box

Supported well for the most part by evergreen browsers. If you need to support older versions of these browsers however, then you may want to feature-detect for support, and optionally include a polyfill instead of the native implementation.

device-pixel-content-box

Surma has a very good article on how this allows us to do pixel perfect rendering. At the time of writing, however this has very limited support. The advices on feature detection for border-box apply here too.

Custom Rounding

By default this hook passes the measured values through Math.round(), to avoid re-rendering on every subpixel changes.

If this is not what you want, then you can provide your own function:

Rounding Down Reported Values

const { ref, width, height } = useResizeObserver<HTMLDivElement>({
  round: Math.floor,
});

Skipping Rounding

import React from "react";
import useResizeObserver from "use-resize-observer";

// Outside the hook to ensure this instance does not change unnecessarily.
const noop = (n) => n;

const App = () => {
  const {
    ref,
    width = 1,
    height = 1,
  } = useResizeObserver<HTMLDivElement>({ round: noop });

  return (
    <div ref={ref}>
      Size: {width}x{height}
    </div>
  );
};

Note that the round option is sensitive to the function reference, so make sure you either use useCallback or declare your rounding function outside of the hook's function scope, if it does not rely on any hook state. (As shown above.)

Getting the Raw Element from the Default RefCallback

Note that "ref" in the above examples is a RefCallback, not a RefObject, meaning you won't be able to access "ref.current" if you need the element itself.

To get the raw element, either you use your own RefObject (see later in this doc), or you can merge the returned ref with one of your own:

import React, { useCallback, useEffect, useRef } from "react";
import useResizeObserver from "use-resize-observer";
import mergeRefs from "react-merge-refs";

const App = () => {
  const { ref, width = 1, height = 1 } = useResizeObserver<HTMLDivElement>();

  const mergedCallbackRef = mergeRefs([
    ref,
    (element: HTMLDivElement) => {
      // Do whatever you want with the `element`.
    },
  ]);

  return (
    <div ref={mergedCallbackRef}>
      Size: {width}x{height}
    </div>
  );
};

Passing in Your Own ref

You can pass in your own ref instead of using the one provided. This can be useful if you already have a ref you want to measure.

const ref = useRef<HTMLDivElement>(null);
const { width, height } = useResizeObserver<HTMLDivElement>({ ref });

You can even reuse the same hook instance to measure different elements:

CodeSandbox Demo

Measuring a raw element

There might be situations where you have an element already that you need to measure. ref now accepts elements as well, not just refs, which means that you can do this:

const { width, height } = useResizeObserver<HTMLDivElement>({
  ref: divElement,
});

Using a Single Hook to Measure Multiple Refs

The hook reacts to ref changes, as it resolves it to an element to observe. This means that you can freely change the custom ref option from one ref to another and back, and the hook will start observing whatever is set in its options.

Opting Out of (or Delaying) ResizeObserver Instantiation

In certain cases you might want to delay creating a ResizeObserver instance.

You might provide a library, that only optionally provides observation features based on props, which means that while you have the hook within your component, you might not want to actually initialise it.

Another example is that you might want to entirely opt out of initialising, when you run some tests, where the environment does not provide the ResizeObserver.

(See discussions)

You can do one of the following depending on your needs:

  • Use the default ref RefCallback, or provide a custom ref conditionally, only when needed. The hook will not create a ResizeObserver instance up until there's something there to actually observe.
  • Patch the test environment, and make a polyfill available as the ResizeObserver. (This assumes you don't already use the polyfilled version, which would switch to the polyfill when no native implementation was available.)

The "onResize" Callback

By the default the hook will trigger a re-render on all changes to the target element's width and / or height.

You can opt out of this behaviour, by providing an onResize callback function, which'll simply receive the width and height of the element when it changes, so that you can decide what to do with it:

import React from "react";
import useResizeObserver from "use-resize-observer";

const App = () => {
  // width / height will not be returned here when the onResize callback is present
  const { ref } = useResizeObserver<HTMLDivElement>({
    onResize: ({ width, height }) => {
      // do something here.
    },
  });

  return <div ref={ref} />;
};

This callback also makes it possible to implement your own hooks that report only what you need, for example:

  • Reporting only width or height
  • Throttle / debounce
  • Wrap in requestAnimationFrame

Hook Composition

As this hook intends to remain low-level, it is encouraged to build on top of it via hook composition, if additional features are required.

Throttle / Debounce

You might want to receive values less frequently than changes actually occur.

CodeSandbox Demo

Breakpoints

Another popular concept are breakpoints. Here is an example for a simple hook accomplishing that.

CodeSandbox Demo

Defaults (SSR)

On initial mount the ResizeObserver will take a little time to report on the actual size.

Until the hook receives the first measurement, it returns undefined for width and height by default.

You can override this behaviour, which could be useful for SSR as well.

const { ref, width = 100, height = 50 } = useResizeObserver<HTMLDivElement>();

Here "width" and "height" will be 100 and 50 respectively, until the ResizeObserver kicks in and reports the actual size.

Without Defaults

If you only want real measurements (only values from the ResizeObserver without any default values), then you can just leave defaults off:

const { ref, width, height } = useResizeObserver<HTMLDivElement>();

Here "width" and "height" will be undefined until the ResizeObserver takes its first measurement.

Container/Element Query with CSS-in-JS

It's possible to apply styles conditionally based on the width / height of an element using a CSS-in-JS solution, which is the basic idea behind container/element queries:

CodeSandbox Demo

Transpilation / Polyfilling

By default the library provides transpiled ES5 modules in CJS / ESM module formats.

Polyfilling is recommended to be done in the host app, and not within imported libraries, as that way consumers have control over the exact polyfills being used.

That said, there's a polyfilled CJS module that can be used for convenience:

import useResizeObserver from "use-resize-observer/polyfilled";

Note that using the above will use the polyfill, even if the native ResizeObserver is available.

To use the polyfill as a fallback only when the native RO is unavailable, you can polyfill yourself instead, either in your app's entry file, or you could create a local useResizeObserver module, like so:

// useResizeObserver.ts
import { ResizeObserver } from "@juggle/resize-observer";
import useResizeObserver from "use-resize-observer";

if (!window.ResizeObserver) {
  window.ResizeObserver = ResizeObserver;
}

export default useResizeObserver;

The same technique can also be used to provide any of your preferred ResizeObserver polyfills out there.

Related

License

MIT

use-resize-observer's People

Contributors

imransl avatar sboudrias avatar semantic-release-bot avatar zeecoder avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

use-resize-observer's Issues

Support getBoundingClientRect()

The returned width and height should also be also available as what we get from el.getBoundingClientRect().width & el.getBoundingClientRect().height sometimes we need exact sitze and i didn't see anything in the Readme that this library supports that already, please correct me if I'm wrong.

Allow passing a ref in

One might have more than one hooks that need to operate on the same ref.
In cases like that, it would be beneficial to allow passing an existing ref in, instead of this hook taking responsibility of creating one.

Pattern could be:

const [ref, width, height] = useResizeObserver();
// vs
const [width, height] = useResizeObserver({ ref });

Need more thought and research on best practices on the topic.

Use polyfill for ResizeObserver

I might not understand the implications for doing that, but would it be possible to use a polyfill for the ResizeObserver so that it would not crash when using on Safari?

The "onResize" callback doesn't work correctly with conditional rendering

I'm using the library with onResize callback because I need to use another reference to DOM element to set width, but I have an issue when the component uses "loading status" so component with associated ref is conditionally rendered.

  return (
    <>
      {displayLoader ? (
        <LoadingBar />
      ) : (
        <Grid ref={measuredBoxRef}>...</Grid>
      )}
    </>
  )

In the library is a dependency for ref in the useEffect hook so when loading state is changed and component is rendered the ResizeObserver doesn't observe this element because the ref is not completely changed (only current value). This problem is described on pages:
https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780
https://dev.to/fnky/pitfalls-of-conditional-rendering-and-refs-in-react-29oi

I can't separate the component like it is proposed in second article, so I'm trying to change the library to use useCallback hook.

Perhaps you met with this problem and you have another solution?

I know that issue is specified for my code but ref in dependencies is not recommended practice and even on the React page is an example to measure DOM element with useCallback hook: https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

Refs not being released on component dismounts leading to memory leaks

I noticed a memory leak in my application when I was using useResizeObserver to observe a <canvas> element: the app was keeping references to the webgl2 contexts and crashed after 15 component remounts since that is the maximum allowed number of detached GL contexts in Chromium

Removing the resize observer on the canvas element fixes the app crashes.

image

I'm using this hook like:

  const canvasRef = useRef<HTMLCanvasElement>(null);
  const observedCanvas = useResizeObserver({
    ref: canvasRef,
  });

// other code

  return (
    <div
      css={{
        flexGrow: 1,
        position: 'relative',
      }}
    >
      <canvas
        ref={canvasRef}
        width={constrained?.width ?? 1}
        height={constrained?.height ?? 1}
        css={{
          background: '#242424',
          objectFit: 'contain',
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
          zIndex: 1,
          pointerEvents: 'none',
        }}
      />
    </div>
  );

Question regarding environments missing ResizeObserver

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.

Build without a polyfill

In some cases you might not need a polyfill, or may want to pick your own.
The library should allow for such a use-case.

Maybe:

import useResizeObserver from 'use-resize-observer/no-polyfill'

Either that or just do that by default.

I'd say most people who'd use this lib would have some way of dealing with polyfills. 🤷‍♂️

Basic usage is not working

Hello there! I'm trying to get the absolute minimum usage example working, and am running into an issue. I have tried on linux and mac, on firefox and chrome, and cannot get it to work.

Here is a code sandbox reproducing it:
https://codesandbox.io/s/exciting-surf-uos1hs?file=/src/App.js

You will see that the onResize function is being called with the new size, however, the width variable returned by useResizeObserver is not getting updated.

Are the terms `blockSize` and `inlineSize` in the docs backwards?

In the docs you have, in a table:

width / undefined | number / The width (or "blockSize") of the element.
height / undefined | number / The height (or "inlineSize") of the element.

With "left to right then top to bottom" layouts, block is vertical and inline is horizontal, so these seem backwards.

Observer is constantly destroyed and replaced when using onResize callback

Hello @ZeeCoder! I've recently started on adding some ResizeObservers into my React app. I found this hook, and it's great, and I love it. Most of the instances of this hook I have added work great, but the last instance of this hook I needed to add before I was done has broken my entire app. What exactly is causing this has been very elusive, and I've spent a few hours trying to track it down. I was able to determine that the bug was not in my code, nor does it have anything to do with the polyfill, and that it is indeed here. After going line-by-line through your source code and adding lots of console.log calls to it, I found it.

The bug is that when using the onResize callback, the hook destroys its existing observer and creates a new one every single time the hook is called, that is, every single time the component is rendered. In the particular case I refer to, this bug breaks my app, and I'll explain how that happens. However, this bug occurs in all cases; it's just that you normally wouldn't notice it.

The reason why this happens is because you have an effect hook that creates and destroys the observer, and that hook has the onResize callback as a dependency. Now, obviously, if the hook's dependencies aren't equal, the hook runs again. Consider the example you wrote in the readme:

import React from "react";
import useResizeObserver from "use-resize-observer";

const App = () => {
  // width / height will not be returned here when the onResize callback is present
  const { ref } = useResizeObserver({
    onResize: ({ width, height }) => {
      // do something here.
    }
  });

  return <div ref={ref} />;
};

Conceptually, the onResize callback is completely static between renders of the component. But here's the kicker: two functions are equal if and only if they are the same object i.e. they have the same memory reference. It actually uses Object.is() to compare dependencies, but this is the case for strict equality too. The function may seem static, but you'll notice that it's defined within the component. As such, a new function is created every single time App renders. This means that in this example and all examples like it, the onResize callback 'changes' on literally every render. It is impossible to pass the same function twice. This causes the observer to be destroyed and re-created on every render.

Now, what you could do is something like this.

import React from "react";
import useResizeObserver from "use-resize-observer";

const onResize = ({ width, height }) => {
  // do something here.
}

const App = () => {
  // width / height will not be returned here when the onResize callback is present
  const { ref } = useResizeObserver({ onResize });
  return <div ref={ref} />;
};

And guess what! The callback is is declared statically, and not just conceptually but literally testing it in my app, doing this completely eliminates the problem.

The case of this bug that is causing so much strife for me is a case where I have infinite render loop, causing everything to hang and crash. Inside the callback, I'm setting the dimensions as state, which causes the component to re-render with that new state. The fact that it re-renders even if the values are the same is an implementation detail of what I'm doing, but because of that, and because ResizeObservers fire once immediately when set to observe an element, this creates a constant loop of re-rendering and creating and destroying the observer.

I note from this test that the ability to change the callback and for it to be called immediately is a feature you deliberately sought to support. However, I'm not sure this actually makes sense. Partially, there is just no good way to do this, because there is no good way to detect an actual change in function in this context, as discussed. But I'm also not sure it makes conceptual sense. I think if I changed the callback of an instance of the hook that already exists, for... some reason (I'm not sure what the use case for this actually is), I would just expect it to get called next time the observer fires; I don't see any intuitive reason for it to be called immediately. The fact that you're destroying and re-creating the whole ResizeObserver when the callback changes I don't think is at all intuitive. So if I were you, I would just remove the callback as a dependency of the effect hook. I think you could use a ref to store the callback as it changes to make it available to the ResizeObserver already in existence.

An additional minor thing I caught which you could perhaps call a bug is that it seems that you intend for the hook to not update anything if the values doesn't actually change. This just doesn't work with the onResize callback. It checks the current values with the previous values, but the previous values are never set in the onResize branch, so that condition is trivially true when using a onResize callback. In theory, if this worked, I would never have had this problem. However, I feel like this check should actually just be done away with. Perhaps you can think of same cases that I can't, but in theory, there should never be any case where a ResizeObserver fires for an element, but that element has not changed size from the last time it fired. I feel like if that ever happens, it's inherently a bug. All this check does is make said bug undetectable.

Simply doesnt seem to want to work with a simple component. Crashes even in the simplest usage.

My code is quite simple.. and yet I can't seem to even get useResizeObserver working in the simplest scenario.

import React from "react";
import useResizeObserver from "use-resize-observer";
import AnnotatedPlayer from 'components/annotated_player/AnnotatedPlayer'

export default function Home() {
	const { ref, width, height } = useResizeObserver<HTMLDivElement>();
	return <>
		<AnnotatedPlayer />
	</>;

}

Crash:
image

I've also tried to pass in a ref (that i create using useref).. then i see a different failure (the call to useResizeObserver then just returns false.

My package.json has the following entry
"use-resize-observer": "^7.0.0",

Here is the actual div that i was going to resize..

export default function Home() {
	const { ref, width, height } = useResizeObserver<HTMLDivElement>();
	return <>
		<div 
		ref={ref}  
		style={{ width: 100, height: 400, resize: "both", background: "red", display: "inline-block", border: "2px solid", overflow: "hidden"}} >
              Size: {width}x{height}
		</div>
	</>;
}

Support in Safari

Need add polyfill for browsers without ResizeObserver (e.x. Safari).

rounding causes trouble...

Rounding the returned dimensions can cause trouble.

For example, I am using your observer to measure an element so I can fit a canvas element inside.
When the available space is fractional, the observer currently rounds up, and my resultant canvas is larger than its parent. Alas - unwanted scrollbars can result!

Maybe consider changing to returning the values without rounding?

Add TypeScript Definitions

I saw that the type definitions for TypeScript was removed due to #12. This is probably due to the not-so-good type definitions I created in a PR. My bad. However, we can still add type definitions using TypeScript's tsconfig.json. There is a setting called declaration that needs to be applied in it like here: https://github.com/frankwallis/react-slidedown/blob/master/tsconfig.json. TypeScript will then generate the TypeDefinitions automatically for us when we run its compiler.

support for SVG elements

Will there be support for SVG elements or not?

Now the error appears. Since the SVG element is not compatible with the HTML element.

TS2322: Type '(instance: HTMLElement | null) => void' is not assignable to type 'LegacyRef<SVGSVGElement>'.   
Type '(instance: HTMLElement | null) => void' is not assignable to type '(instance: SVGSVGElement | null) => void'.     
Types of parameters 'instance' and 'instance' are incompatible.       
Type 'SVGSVGElement | null' is not assignable to type 'HTMLElement | null'.         
Type 'SVGSVGElement' is missing the following properties from type 'HTMLElement': 
accessKey, accessKeyLabel, autocapitalize, dir, and 19 more.

Support for wrapped references?

I like the API of this library a lot. Being able to pass in existing refs in combination with the onResize callback gives a lot of flexibility.

I'm wondering if it would be possible to support nested references as well. To illustrate the use case: I had a situation where I had two mutable refs related to a canvas:

  • the useRef<HTMLCanvasElement>(null) itself.
  • and some useRef<HitboxData>([]) which stores information on the stuff drawn onto the canvas for hitbox testing in mouse events.

I started to pull the two things into one common mutable ref, but I have the impression that useResizeObserver doesn't allow such a refactoring. For instance:

import React, { useRef } from "react";
import ReactDOM from "react-dom";

import useResizeObserver from "use-resize-observer";

// All mutable state related to canvas fused into one type
type CanvasState = {
  canvas: HTMLCanvasElement;
  // Other stateful data related to canvas, e.g., data needed for hitbox testing.
  hitboxes: Path2D[];
};

function Canvas() {
  const canvasStateRef = useRef<CanvasState | null>(null);

  useResizeObserver<HTMLCanvasElement>({
    ref: canvasStateRef.current?.canvas,
    onResize: ({ width, height }) => {
      console.log(`Resized to ${width} x ${height}`);
      // Omitting code for re-rendering canvas...
    },
  });

  return (
    <canvas
      ref={(ref) => {
        if (canvasStateRef.current == null && ref != null) {
          canvasStateRef.current = {
            canvas: ref,
            hitboxes: [],
          };
        }
      }}
      style={{
        width: "200px",
        height: "200px",
      }}
    />
  );
}

ReactDOM.render(
  <React.StrictMode>
    <Canvas />
  </React.StrictMode>,
  document.getElementById("root")
);

This compiles, but obviously doesn't work, because canvasStateRef.current?.canvas is still null at the time it is passed to useResizeObserver. The following variant would be more correct but doesn't compile, because the type CanvasState doesn't satisfy the HTMLElement interface.

useResizeObserver<CanvasState>({
  ref: canvasStateRef,
  onResize: ({ width, height }) => {
    console.log(`Resized to ${width} x ${height}`);
  },
});

I'm wondering if such a use case could be supported by passing in a transformation function that allows to extract the HTMLElement from an arbitrary RefObject<T>. For instance:

useResizeObserver<CanvasState>({
  ref: canvasStateRef,
  extractHtmlElement: (canvasState: CanvasState) => canvasState.canvas,
  onResize: ({ width, height }) => {
    console.log(`Resized to ${width} x ${height}`);
  },
});

No values reported when using React StrictMode

Hi,

I am using this hook in one of my projects in conjunction with React 18 and Strict Mode. However, it appears that the hook does not report any values when used together with React Strict Mode.

See the following CodeSandbox for a demonstration of this issue.

This issue is caused by the fact that React Strict Mode renders components twice in development mode in order to detect problems. In particular, this behavior causes problems with the following logic (for tracking unmounts):

useEffect(() => {
return () => {
didUnmount.current = true;
};
}, []);

Because the component is rendered twice, the component is marked as unmounted and no further size updates are reported by the hook.

I believe the following patch should resolve this issue:

  const didUnmount = useRef(false);
  useEffect(() => {
+   didUnmount.current = false;
    return () => {
      didUnmount.current = true;
    };
  }, []);

Externalise useResolvedElement

Hi there, thanks for this great hook. Could you eventually release useResolvedElement as an external library? It would be good to reuse this without copying its function code.

Padding not taken into account ?

A little question,

Is that normal that padding isn't taken into account in the returned height value ?

NB: I have the rule * {box-sizing:border-box;} if ever it matters

Thank you

No way to set box sizing

Bug Report

I need to be able to set the box that resize observers uses

Describe the Bug

Right now I can't get it to respect box-sizing: border-box

How to Reproduce

Try to size an element with box-sizing: border-box

Expected Behavior

Should be able to make it take padding into account

How should I use the use-resize-observer in case of useCallback instead of useRef

What if I have useCallback instead of useRef, according to https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

const MyComponent = () => {
    const editorContainerRef = useCallback( editorContainer =>{
        //editorContainer here is a dom node already
    },[])

   return React.createElement('div',{
        ref: editorContainerRef,
        id: 'editor-container',
    })
}

I probably can fake it by creating {current:editorContainer} and passing that to usResizeObserver like this

const { width, height } = useResizeObserver({ ref: {current:editorContainer} as React.RefObject<HTMLElement> });

but may be there is a different approach?

Merge ref and callbackRef

There's no good reason I can think of why the returned ref couldn't have been a callback ref.

That way the API gets simplified and usage is easier as users don't have to worry about whether their component will mount with a delay or not.

Rounded widths and heights

Would it be possible to use getBoundingClientRect() to measure widths and heights, so that the values are not rounded? I have run into situations where not having the floating-point values has caused issues when using those measurements. If there is a situation where users may want the rounded value that I am not thinking of, then having an option to get the floating-point values would also work.

Defaults are unnecessary code

I'm wondering about the defaults, because they seem to provide nothing of value that can't be done with just JavaScript:

const { ref, width = 100, height = 50 } = useResizeObserver()

This gives 100 x 50 until client side code kicks in.

It would make sense to encourage people to use JS features and keep hook's feature set and size as small as possible. (Unless I'm wrong here and I'm simply used to some extra feature not commonly available in most codebases.)

onResize is not called on mount

onResize is not called on mount in case when useResizeObserver is given an external ref

const ref = useRef<HTMLDivElement | null>(null)
	useResizeObserver({
		onResize: ({ height = 0, width = 0 }) => {
			setPanelHeight(height)
			setPanelWidth(width)
		},
		ref,
	})

vs

	const { ref } = useResizeObserver({
		onResize: ({ height = 0, width = 0 }) => {
			setPanelHeight(height)
			setPanelWidth(width)
		},
	})

Type definitions for TypeScript

I am using use-resize-observer with TypeScript and noticed there aren't any type definitions for it. It will be great, if it is included with the npm package. I have already created a small definitions file for our project to get it to work with TypeScript. I'll create a pull request with those changes.

SSR HTMLDivElement is not defined in 7.0.0

I'm using Nextjs and I noticed that when I use useResizeObserver in version 7.0.0 it throws the following error:

image

But if I use version 6.1.0 it works alright
Haven't checked in other versions

ref cannot be used on div or canvas in TypeScript

When trying to assign the ref to the ref prop on a div, as in the examples:

Type 'RefObject<HTMLElement>' is not assignable to type 'LegacyRef<HTMLDivElement>'.
  Type 'RefObject<HTMLElement>' is not assignable to type 'RefObject<HTMLDivElement>'.
    Property 'align' is missing in type 'HTMLElement' but required in type 'HTMLDivElement'.ts(2322)
lib.dom.d.ts(6346, 5): 'align' is declared here.
index.d.ts(96, 9): The expected type comes from property 'ref' which is declared here on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'

When trying to assign the ref to the ref prop on a canvas:

Type 'RefObject<HTMLElement>' is not assignable to type 'LegacyRef<HTMLCanvasElement>'.
  Type 'RefObject<HTMLElement>' is not assignable to type 'RefObject<HTMLCanvasElement>'.
    Type 'HTMLElement' is missing the following properties from type 'HTMLCanvasElement': height, width, getContext, toBlob, and 2 more.ts(2322)
index.d.ts(96, 9): The expected type comes from property 'ref' which is declared here on type 'DetailedHTMLProps<CanvasHTMLAttributes<HTMLCanvasElement>, HTMLCanvasElement>'

Add optional effect to useResizeObserver

We've been using useResizeObserver to track when to trigger a side effect, and so end up with code like this:

  const axisRef = useRef();
  const cursorRef = useRef();
  const { width: axisWidth } = useResizeObserver({ ref: axisRef });

  // Reset the axis if the width changes
  useEffect(() => {
    if (cursorRef.current !== null) {
      cursorRef.current.reset();
    }
  }, [axisWidth]);

It would be convenient if we could simply provide this effect to the resize observer.

ResizeObserver loop limit exceeded

👋 Hello. We have a component that’s using this hook with in it’s default form (no custom callback) and have been getting a lot of ResizeObserver loop limit exceeded errors thrown during usage.

From my brief search around the internet it seems as though this is harmless but it does add a lot of noise in common usage and that that noise could be avoided by wrapping the ResizeObserver callback in a requestAnimationFrame.

I could adjust our usage to do this using a custom callback, but that would mean reimplementing the core observer logic which seems rather pointless. Does it make sense as a core adjustment?

Dynamic array of elements

I'm rendering an array of elements and need to capture the width of each element individually.

This hook works perfectly for a single element but is there a way to capture the sizes of a dynamic amount of elements?

I saw the example of reusing the hook by passing in custom refs but that example requires you to create all refs up front which wouldn't be possible with a dynamic amount of elements.

Any advice 🙏?

export TS types

I have a throttled onResize handler. I found that unless I wrap in a useCallback, the throttling doesn't take effect because the resize handler triggers a dimension change, which triggers a rerender, which replaces the onResize handler with a new one (starting a fresh throttle timer).

However, moving out the onResize handler means I lose out on some type inference. I don't love copying over types and would prefer to import them. Looks like this library declares types but doesn't export them, so I can't use them directly.

image

image

Here's my current workaround:
image

Offering throttled- and debounced variants

Instead of having them as options, I think it would be better to ship composing hooks, basically as they are outlined in the readme already:

This would allow me to keep the base interface minimalistic, while at the same time offer solutions to a seemingly frequent use case.

This is how it would look I think:

import useResizeObserver from 'use-resize-observer/throttled';
import useResizeObserver from 'use-resize-observer/debounced';

// Where the API / options are the exact same.

The only downside that I would probably have to provide polyfilled builds as well, so maybe:

import useResizeObserver from 'use-resize-observer/throttled/polyfilled';
import useResizeObserver from 'use-resize-observer/debounced/polyfilled';

And of course I would have to run the whole test suite and more on these builds.

I'll just leave this issue here to assess demand.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.