Skip to content

Commit 95960ce

Browse files
committed
ResizeObserver instances are no longer created unnecessarily
When the onResize callback changes. (Fixes #32) Using webpack through karma to build tests instead of using parcel separately.
1 parent 529269c commit 95960ce

File tree

10 files changed

+357
-110
lines changed

10 files changed

+357
-110
lines changed

.size-limit.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
[
22
{
33
"path": "dist/bundle.esm.js",
4-
"limit": "323 B",
4+
"limit": "353 B",
55
"gzip": true
66
},
77
{
88
"path": "dist/bundle.esm.js",
9-
"limit": "243 B",
9+
"limit": "266 B",
1010
"brotli": true
1111
},
1212
{
1313
"path": "dist/bundle.cjs.js",
14-
"limit": "313 B",
14+
"limit": "341 B",
1515
"gzip": true
1616
},
1717
{
1818
"path": "dist/bundle.cjs.js",
19-
"limit": "233 B",
19+
"limit": "258 B",
2020
"brotli": true
2121
},
2222
{
2323
"path": "polyfilled.js",
24-
"limit": "2644 B",
24+
"limit": "2673 B",
2525
"gzip": true
2626
},
2727
{
2828
"path": "polyfilled.js",
29-
"limit": "2353 B",
29+
"limit": "2381 B",
3030
"brotli": true
3131
}
3232
]

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## 6.1.0-alpha2
4+
5+
- ResizeObserver instances are no longer created unnecessarily when the onResize
6+
callback changes. (Fixes #32)
7+
- Written new tests in [react testing library](https://github.com/testing-library/react-testing-library).
8+
39
## 6.1.0-alpha1
410

511
- Rewrote the source in TypeScript. (Feedback is welcome.)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A React hook that allows you to use a ResizeObserver to measure an element's siz
88
## Highlights
99

1010
- Written in **TypeScript**.
11-
- **Tiny**: 323 B (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit).
11+
- **Tiny**: 353 B (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit).
1212
- Exposes an **onResize callback** if you need more control.
1313
- [Throttle / Debounce](#throttle--debounce)
1414
- Works with **SSR**.

karma.conf.js

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,60 @@
1-
module.exports = function(config) {
1+
module.exports = function (config) {
22
const browsers = (process.env.KARMA_BROWSERS || "ChromeHeadless").split(",");
33

4+
const testFilePattern = "tests/*.tsx";
5+
46
config.set({
57
basePath: ".",
68
frameworks: ["jasmine"],
7-
files: ["tests/dist/index.js"],
9+
files: [
10+
{
11+
pattern: testFilePattern,
12+
watched: true,
13+
},
14+
],
815
autoWatch: true,
916

1017
browsers,
1118
reporters: ["spec"],
19+
preprocessors: {
20+
[testFilePattern]: ["webpack", "sourcemap"],
21+
},
1222

1323
// Max concurrency for SauceLabs OS plan
1424
concurrency: 5,
1525

1626
client: {
1727
jasmine: {
1828
// Order of the tests matter, so don't randomise it
19-
random: false
20-
}
21-
}
29+
random: false,
30+
},
31+
},
32+
33+
webpack: {
34+
mode: "development",
35+
devtool: "inline-source-map",
36+
module: {
37+
rules: [
38+
{
39+
test: /\.(ts|tsx|js|jsx)$/,
40+
exclude: /node_modules/,
41+
use: {
42+
loader: "babel-loader",
43+
options: {
44+
presets: [
45+
["@babel/preset-env", { loose: true, modules: false }],
46+
"@babel/preset-react",
47+
"@babel/preset-typescript",
48+
],
49+
plugins: [["@babel/transform-runtime", { useESModules: true }]],
50+
},
51+
},
52+
},
53+
],
54+
},
55+
resolve: {
56+
extensions: [".ts", ".tsx", ".js", ".jsx"],
57+
},
58+
},
2259
});
2360
};

package.json

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "use-resize-observer",
3-
"version": "6.1.0-alpha.1",
3+
"version": "6.1.0-alpha.2",
44
"main": "dist/bundle.cjs.js",
55
"module": "dist/bundle.esm.js",
66
"types": "dist/index.d.ts",
@@ -10,22 +10,19 @@
1010
"author": "Viktor Hubert <[email protected]>",
1111
"license": "MIT",
1212
"scripts": {
13-
"build": "run-s src:build test:build",
14-
"watch": "KARMA_BROWSERS=Chrome run-p src:watch test:watch karma:watch",
15-
"src:build": "rollup -c && tsc && cp dist/index.d.ts polyfilled.d.ts",
13+
"build": "rollup -c && tsc && cp dist/index.d.ts polyfilled.d.ts",
14+
"watch": "KARMA_BROWSERS=Chrome run-p 'src:watch' 'karma:watch'",
1615
"src:watch": "rollup -c -w",
17-
"tsc": "tsc",
18-
"size-limit": "size-limit",
19-
"test": "run-s 'build' 'size-limit' 'tsc -p tests' 'test:chrome:headless' 'test:firefox:headless'",
20-
"test:build": "parcel build tests/index.tsx --out-dir tests/dist",
21-
"test:watch": "parcel watch tests/index.ts --out-dir tests/dist",
16+
"check:size": "size-limit",
17+
"check:tests": "tsc -p tests",
18+
"test": "run-s 'build' 'check:size' 'check:tests' 'test:headless:*'",
2219
"test:chrome": "KARMA_BROWSERS=Chrome yarn karma:run",
23-
"test:chrome:headless": "KARMA_BROWSERS=ChromeHeadless yarn karma:run",
20+
"test:headless:chrome": "KARMA_BROWSERS=ChromeHeadless yarn karma:run",
2421
"test:firefox": "KARMA_BROWSERS=Firefox yarn karma:run",
25-
"test:firefox:headless": "KARMA_BROWSERS=FirefoxHeadless yarn karma:run",
22+
"test:headless:firefox": "KARMA_BROWSERS=FirefoxHeadless yarn karma:run",
2623
"karma:run": "karma start --singleRun",
2724
"karma:watch": "karma start",
28-
"prepublish": "yarn src:build"
25+
"prepublish": "yarn build"
2926
},
3027
"husky": {
3128
"hooks": {
@@ -43,25 +40,29 @@
4340
},
4441
"devDependencies": {
4542
"@babel/core": "^7.7.7",
43+
"@babel/plugin-transform-runtime": "^7.9.0",
4644
"@babel/preset-env": "^7.7.7",
45+
"@babel/preset-react": "^7.9.4",
4746
"@babel/preset-typescript": "^7.9.0",
4847
"@rollup/plugin-inject": "^4.0.1",
4948
"@size-limit/preset-small-lib": "^4.4.5",
49+
"@testing-library/react": "^10.0.2",
5050
"@types/karma": "^5.0.0",
5151
"@types/karma-jasmine": "^3.1.0",
5252
"@types/react": "^16.9.34",
5353
"@types/react-dom": "^16.9.6",
54-
"babel-regenerator-runtime": "^6.5.0",
54+
"babel-loader": "^8.1.0",
5555
"delay": "^4.1.0",
5656
"husky": "^4.2.5",
5757
"karma": "^5.0.1",
5858
"karma-chrome-launcher": "^3.0.0",
5959
"karma-firefox-launcher": "^1.3.0",
6060
"karma-jasmine": "^3.1.1",
61+
"karma-sourcemap-loader": "^0.3.7",
6162
"karma-spec-reporter": "^0.0.32",
63+
"karma-webpack": "^4.0.2",
6264
"lint-staged": "^10.1.3",
6365
"npm-run-all": "^4.1.5",
64-
"parcel-bundler": "^1.10.3",
6566
"prettier": "^2.0.4",
6667
"react": "^16.9.0",
6768
"react-dom": "^16.9.0",

src/index.ts

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { useEffect, useState, useRef, useMemo, RefObject } from "react";
1+
import {
2+
useEffect,
3+
useState,
4+
useRef,
5+
useMemo,
6+
RefObject,
7+
MutableRefObject,
8+
} from "react";
29

310
type ObservedSize = {
411
width: number | undefined;
@@ -29,8 +36,51 @@ function useResizeObserver<T>(
2936
// @see https://reactjs.org/docs/hooks-rules.html#explanation
3037
const defaultRef = useRef<T>(null);
3138

32-
const ref = opts.ref || defaultRef;
39+
// Saving the callback as a ref. With this, I don't need to put onResize in the
40+
// effect dep array, and just passing in an anonymous function without memoising
41+
// will not reinstantiate the hook's ResizeObserver
3342
const onResize = opts.onResize;
43+
const onResizeRef = useRef<ResizeHandler | undefined>(undefined);
44+
onResizeRef.current = onResize;
45+
46+
// Using a single instance throughought the hook's lifetime
47+
const resizeObserverRef = useRef<ResizeObserver>() as MutableRefObject<
48+
ResizeObserver
49+
>;
50+
if (!resizeObserverRef.current) {
51+
resizeObserverRef.current = new ResizeObserver((entries) => {
52+
if (!Array.isArray(entries)) {
53+
return;
54+
}
55+
56+
// Since we only observe the one element, we don't need to loop over the
57+
// array
58+
if (!entries.length) {
59+
return;
60+
}
61+
62+
const entry = entries[0];
63+
64+
// `Math.round` is in line with how CSS resolves sub-pixel values
65+
const newWidth = Math.round(entry.contentRect.width);
66+
const newHeight = Math.round(entry.contentRect.height);
67+
if (
68+
previous.current.width !== newWidth ||
69+
previous.current.height !== newHeight
70+
) {
71+
const newSize = { width: newWidth, height: newHeight };
72+
if (onResizeRef.current) {
73+
onResizeRef.current(newSize);
74+
} else {
75+
previous.current.width = newWidth;
76+
previous.current.height = newHeight;
77+
setSize(newSize);
78+
}
79+
}
80+
});
81+
}
82+
83+
const ref = opts.ref || defaultRef;
3484
const [size, setSize] = useState<{
3585
width?: number;
3686
height?: number;
@@ -60,41 +110,11 @@ function useResizeObserver<T>(
60110
}
61111

62112
const element = ref.current;
63-
const resizeObserver = new ResizeObserver((entries) => {
64-
if (!Array.isArray(entries)) {
65-
return;
66-
}
67-
68-
// Since we only observe the one element, we don't need to loop over the
69-
// array
70-
if (!entries.length) {
71-
return;
72-
}
73-
74-
const entry = entries[0];
75-
76-
// `Math.round` is in line with how CSS resolves sub-pixel values
77-
const newWidth = Math.round(entry.contentRect.width);
78-
const newHeight = Math.round(entry.contentRect.height);
79-
if (
80-
previous.current.width !== newWidth ||
81-
previous.current.height !== newHeight
82-
) {
83-
const newSize = { width: newWidth, height: newHeight };
84-
if (onResize) {
85-
onResize(newSize);
86-
} else {
87-
previous.current.width = newWidth;
88-
previous.current.height = newHeight;
89-
setSize(newSize);
90-
}
91-
}
92-
});
93113

94-
resizeObserver.observe(element);
114+
resizeObserverRef.current.observe(element);
95115

96-
return () => resizeObserver.unobserve(element);
97-
}, [ref, onResize]);
116+
return () => resizeObserverRef.current.unobserve(element);
117+
}, [ref]);
98118

99119
return useMemo(() => ({ ref, width: size.width, height: size.height }), [
100120
ref,

0 commit comments

Comments
 (0)