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

Support for wrapped references? #59

Closed bluenote10 closed 3 years ago

bluenote10 commented 3 years ago

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:

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}`);
  },
});
ZeeCoder commented 3 years ago

Hi, glad you like the API, it was built very intentionally. :ok_hand: Sounds like what you need to use here is the callback ref the hook provides you by default:

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);

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

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

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

:point_up: Haven't tested this, just typed it out here, but feel free to create a codesandbox if it doesn't work for me to take a better look at. :+1:

bluenote10 commented 3 years ago

Yes indeed, looks I misunderstood the purpose of this callback ref (I thought it gets called by the resize observer when the ref becomes active, not that it has "register ref" semantics).

This seems to work fine, thanks :+1:

ZeeCoder commented 3 years ago

🙌

On Wed, 27 Jan 2021, 17:22 Fabian Keller, notifications@github.com wrote:

Yes indeed, looks I misunderstood the purpose of this callback ref (I thought it gets called by the resize observer when the ref becomes active, not that it has "register ref" semantics).

This seems to work fine, thanks 👍

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/ZeeCoder/use-resize-observer/issues/59#issuecomment-768401370, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA4CKES6KX3OZA6HRQXRMFTS4A4TTANCNFSM4WR7KUAA .