diff --git a/README.md b/README.md
index 8e36339e..60314abc 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,7 @@
- [`useStartTyping`](./docs/useStartTyping.md) — detects when user starts typing.
- [`useWindowScroll`](./docs/useWindowScroll.md) — tracks `Window` scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usewindowscroll--docs)
- [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668)
+ - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver)
- [**UI**](./docs/UI.md)
diff --git a/docs/useMeasure.md b/docs/useMeasure.md
new file mode 100644
index 00000000..64123f24
--- /dev/null
+++ b/docs/useMeasure.md
@@ -0,0 +1,23 @@
+# `useSize`
+
+React sensor hook that tracks size of an HTML element.
+
+## Usage
+
+```jsx
+import { useMeasure } from "react-use";
+
+const Demo = () => {
+ const [ref, { width, height }] = useSize();
+
+ return (
+
+
width: {width}
+
height: {height}
+
+ );
+};
+```
+## Related hooks
+
+- [useSize](./useSize.md)
\ No newline at end of file
diff --git a/package.json b/package.json
index 4bb3a3de..c1d5906a 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
"nano-css": "^5.1.0",
"react-fast-compare": "^2.0.4",
"react-wait": "^0.3.0",
+ "resize-observer-polyfill": "^1.5.1",
"screenfull": "^4.1.0",
"throttle-debounce": "^2.0.1",
"ts-easing": "^0.2.0"
diff --git a/src/__tests__/useMeasure.test.ts b/src/__tests__/useMeasure.test.ts
new file mode 100644
index 00000000..e12136e2
--- /dev/null
+++ b/src/__tests__/useMeasure.test.ts
@@ -0,0 +1,84 @@
+import { act, renderHook } from '@testing-library/react-hooks';
+import useMeasure, { ContentRect } from '../useMeasure';
+
+interface Entry {
+ target: HTMLElement;
+ contentRect: ContentRect;
+}
+
+jest.mock('resize-observer-polyfill', () => {
+ return class ResizeObserver {
+ private cb: (entries: Entry[]) => void;
+ private map: WeakMap;
+ private targets: HTMLElement[];
+ constructor(cb: () => void) {
+ this.cb = cb;
+ this.map = new WeakMap();
+ this.targets = [];
+ }
+ public disconnect() {
+ this.targets.map(target => {
+ const originMethod = this.map.get(target);
+ target.setAttribute = originMethod;
+ this.map.delete(target);
+ });
+ }
+ public observe(target: HTMLElement) {
+ const method = 'setAttribute';
+ const originMethod = target[method];
+ this.map.set(target, originMethod);
+ this.targets.push(target);
+ target[method] = (...args) => {
+ const [attrName, value] = args;
+ if (attrName === 'style') {
+ const rect: ContentRect = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 };
+ value.split(';').map(kv => {
+ const [key, v] = kv.split(':');
+ if (['top', 'bottom', 'left', 'right', 'width', 'height'].includes(key)) {
+ rect[key] = parseInt(v, 10);
+ }
+ });
+ target.getBoundingClientRect = () => rect;
+ }
+ originMethod.apply(target, args);
+ this.fireCallback();
+ };
+ }
+ private fireCallback() {
+ if (this.cb) {
+ this.cb(
+ this.targets.map(target => {
+ return {
+ target,
+ contentRect: target.getBoundingClientRect(),
+ };
+ })
+ );
+ }
+ }
+ };
+});
+
+it('reacts to changes in size of any of the observed elements', () => {
+ const { result } = renderHook(() => useMeasure());
+ const div = document.createElement('div');
+ result.current[0](div);
+ expect(result.current[1]).toMatchObject({
+ width: 0,
+ height: 0,
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ });
+ act(() => div.setAttribute('style', 'width:200px;height:200px;top:100;left:100'));
+
+ expect(result.current[1]).toMatchObject({
+ width: 200,
+ height: 200,
+ top: 100,
+ bottom: 0,
+ left: 100,
+ right: 0,
+ });
+});
diff --git a/src/useMeasure.ts b/src/useMeasure.ts
new file mode 100644
index 00000000..6a68d015
--- /dev/null
+++ b/src/useMeasure.ts
@@ -0,0 +1,42 @@
+import { useCallback, useState } from 'react';
+import ResizeObserver from 'resize-observer-polyfill';
+
+export interface ContentRect {
+ width: number;
+ height: number;
+ top: number;
+ right: number;
+ left: number;
+ bottom: number;
+}
+const useMeasure = (): [(instance: T) => void, ContentRect] => {
+ const [rect, set] = useState({
+ width: 0,
+ height: 0,
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ });
+
+ const [observer] = useState(
+ () =>
+ new ResizeObserver(entries => {
+ const entry = entries[0];
+ set(entry.contentRect);
+ })
+ );
+
+ const ref = useCallback(
+ node => {
+ observer.disconnect();
+ if (node) {
+ observer.observe(node);
+ }
+ },
+ [observer]
+ );
+ return [ref, rect];
+};
+
+export default useMeasure;