react-measured-dom
TypeScript icon, indicating that this package has built-in type declarations

1.1.1 • Public • Published

react-measured-dom

Build Status

Zero-dependency React bounding box providers and hooks for DOM elements.

Why react-measured-dom?

Sometimes you need access to size and/or position of a component DOM element, typically:

  • You are using a library such as react-window, react-virtualized or react-vtree and need numeric width and height in order to render your components
  • You are rendering responsive charts (or WebGL)
  • You are visually relating components that are unrelated in DOM
  • You are implementing a component that changes its behavior depending on its size and/or position
  • ...

In these cases you usually end up using some sort of stateful component logic based on the resize event of the window object. The problem with this solution is that your layout might change even though window dimensions haven't.

That is where react-measured-dom comes in!

In this guide

Quick example

import React from 'react';
import { Measured } from 'react-measured-dom';
 
const MyComponent: React.FC = () => (
  <Measured.div className='full-size'>
    {box => (
      {/* Let's say you are rendering a Chart */}
      <Chart width={box.width} height={box.height} />
    )}
  </Measured.div>
);

Check out the API docs to get started!

Installation

react-measured-dom is available as an NPM module (along with its TypeScript definitions):

# For npm users 
npm install --save react-measured-dom
 
# For yarn users 
yarn add react-measured-dom

API Documentation

Measured

import { Measured } from 'react-measured-dom';

Using predefined DOM elements

Probably the most interesting export that gives you instant access to all pre-wrapped HTML elements:

import React from 'react';
import { Measured } from 'react-measured-dom';
 
const MyComponent: React.FC = () => (
  <>
    <Measured.div />
    <Measured.span />
    <Measured.em />
    <Measured.aside />
    {/* See below for all supported elements */}
  </>
);

Measured exposes the following HTML elements:

a, abbr, address, article, aside, b, bdi, bdo, big, blockquote, body, button, canvas, caption, cite, code, dd, del, details, dfn, dialog, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, i, iframe, img, input, ins, kbd, label, legend, li, main, map, mark, meter, nav, object, ol, output, p, picture, pre, progress, q, rp, rt, ruby, s, samp, section, select, small, span, strong, sub, summary, sup, table, tbody, td, textarea, tfoot, th, thead, time, tr, u, ul, var, video,

Using Measured as HOC

If these are not enough (for example you want to wrap an existing component for measurements) you can go the HOC way:

import React from 'react';
import Label from '../your/label/component';
import { Measured } from 'react-measured-dom';
 
const MeasuredLabel = Measured(Label);
 
const MyComponent: React.FC = () => (
  // MeasuredLabel component accepts all the original props from Label
  <MeasuredLabel someLabelProp="value">
    {box => {
      // Use the bounding box here
    }}
  </MeasuredLabel>
);

IMPORTANT The ref of the wrapped element must be a ref to a HTMLElement!

Using Measured with styled-components

Since react-measured-dom's only requirement is for the wrapped element to expose a ref containing an HTMLElement, it is possible to use it in conjunction with styled-components as well:

import React from 'react';
import styled from 'styled-components';
import { Measured } from 'react-measured-dom';
 
const StyledComponent = styled.div`
  ...
`;
 
const MeasuredStyledComponent = Measured(StyledComponent);
 
const MyComponent: React.FC = () => (
  <MeasuredStyledComponent>
    {box => {
      // Use the bounding box here
    }}
  </MeasuredStyledComponent>
);

Besides the props of the wrapped element, Measured accepts the following props:

children

children?: ReactNode | (box: BoundingBox) => ReactNode

Although Measured.div can accept ReactNode as children, the default use case will most probably look like:

const MyComponent = () => (
  <Measured.div>
    {boundingBox => (
      <Chart width={boundingBox.width} height={boundingBox.height}>
    )}
  </Measured.div>
)

In this case the children prop of Measured.div is a render function that receives a single parameter, boundingBox (see BoundingBox below).

onBoundingBoxChange

onBoundingBoxChange?: (props: BoundingBox | undefined) => void

In some cases you want to make the bounding box available to a parent component, then most probably store it in a component state somewhere. For this you can use the onBoundingBoxChange prop:

const MyComponent = () => {
  const [boundingBox, setBoundingBox] = useState<BoundingBox>();
  const className = boundingBox && boundingBox.top < 0 ? "highlighted" : null
 
  <div>
    <Measured.div onBoundingBoxChange={setBoundingBox}>
      ... Some children here, can also be a callback ...
    </Measured.div>
 
    <div className={className}>
      And the bounding box gets used somewhere else
    </div>
  </div>
}

This prop gets called everytime the bounding box changes. When the component unmounts it gets called with undefined.

positionOnly

positionOnly?: boolean

If you are not interested in the element dimensions and only want to rerender when the position changes you can pass truthy positionOnly:

const MyComponent = () => (
  <Measured.div positionOnly>
    {boundingBox => {
      // boundingBox will now have width & height set to NaN
    }}
  </Measured.div>
)

In this case the width and height of the boundingBox (in both children and onBoundingBoxChange callback) will be set to NaN.

sizeOnly

sizeOnly?: boolean

If you are not interested in the element position and only want to rerender when the position changes you can pass truthy sizeOnly:

const MyComponent = () => (
  <Measured.div sizeOnly>
    {boundingBox => {
      // boundingBox will now have top, right, bottom and left set to NaN
    }}
  </Measured.div>
)

In this case the top, right, bottom and left of the boundingBox (in both children and onBoundingBoxChange callback) will be set to NaN.

IMPORTANT For most of the use cases you want to turn sizeOnly on since you don't want to be rerendering your charts or lists whenever e.g. the user scrolls the page.

useBoundingBox

import { useBoundingBox } from 'react-measured-dom';

Sometimes you just want a more fine grained access to react-measured-dom capabilities. In those cases you can skip the HOC approach and measure a ref to an HTMLElement using useBoundingBox:

const MyComponent = () => {
  const ref = useRef<HTMLElement>();
  const boundingBox: BoundingBox | undefined = useBoundingBox(ref);
 
  // You can either use the boudingBox here or pass it around
  // just remember, on the first render it is going to be `undefined`
 
  return <div ref={ref}>
    {/* ... */}
  </div>
}

Basic API

useBoundingBox: (
  ref: React.RefObject<HTMLElement>,
  onChange?: (box: BoundingBox | undefined) => void,
)

Using transforms

Sometimes you might want to tweak the measured size/position values depending on external factors (e.g. to discard size or position in order to optimise rendering). For that you can pass an array of transforms applied to the original measured BoundingBox.

// For ease of use falsy values are also allowed
type CheckerTransform = (box: BoundingBox) => BoundingBox;
 
useBoundingBox: (
  ref: React.RefObject<HTMLElement>,
  onChange?: (box: BoundingBox | undefined) => void,
  transform?: CheckerTransform
) => BoundingBox

These will be applied left to right, the return value of one passed to the next.

BoundingBox

A data container for information about a bounding box. Looks like:

interface BoundingBox {
  readonly top: number;
  readonly right: number;
  readonly bottom: number;
  readonly left: number;
  readonly width: number;
  readonly height: number;
}

IMPORTANT top, right, bottom and left are all measured from the left top corner of the reference element, they do not act like e.g. CSS bottom or right values that are measured relative to the bottom edge of the container.

Issues

Please use the github issue tracker to submit any bugs or feature requests.

Package Sidebar

Install

npm i react-measured-dom

Weekly Downloads

25

Version

1.1.1

License

MIT

Unpacked Size

31.2 kB

Total Files

9

Last publish

Collaborators

  • jannanista