react-measured-dom
v1.1.1
Published
Width & height providers for React
Downloads
56
Maintainers
Readme
react-measured-dom
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
orreact-vtree
and need numericwidth
andheight
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
- Installation
- API Docs
Measured
Measured
for DOM nodesMeasured
as HOCMeasured
withstyled-components
Measured
element props
useBoundingBox
useBoundingBox
basic APIuseBoundingBox
transforms
BoudingBox
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.