Skip to content

Commit

Permalink
ResizeObserver instances are no longer created unnecessarily
Browse files Browse the repository at this point in the history
When the onResize callback changes. (Fixes #32)
Using webpack through karma to build tests instead of using parcel separately.
  • Loading branch information
ZeeCoder committed Apr 20, 2020
1 parent 529269c commit 5ab3cc9
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 110 deletions.
12 changes: 6 additions & 6 deletions .size-limit.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
[
{
"path": "dist/bundle.esm.js",
"limit": "323 B",
"limit": "353 B",
"gzip": true
},
{
"path": "dist/bundle.esm.js",
"limit": "243 B",
"limit": "266 B",
"brotli": true
},
{
"path": "dist/bundle.cjs.js",
"limit": "313 B",
"limit": "341 B",
"gzip": true
},
{
"path": "dist/bundle.cjs.js",
"limit": "233 B",
"limit": "258 B",
"brotli": true
},
{
"path": "polyfilled.js",
"limit": "2644 B",
"limit": "2673 B",
"gzip": true
},
{
"path": "polyfilled.js",
"limit": "2353 B",
"limit": "2381 B",
"brotli": true
}
]
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## 6.1.0-alpha2

- ResizeObserver instances are no longer created unnecessarily when the onResize
callback changes. (Fixes #32)
- Written new tests in [react testing library](https://github.com/testing-library/react-testing-library).

## 6.1.0-alpha1

- Rewrote the source in TypeScript. (Feedback is welcome.)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A React hook that allows you to use a ResizeObserver to measure an element's siz
## Highlights

- Written in **TypeScript**.
- **Tiny**: 323 B (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit).
- **Tiny**: 353 B (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit).
- Exposes an **onResize callback** if you need more control.
- [Throttle / Debounce](#throttle--debounce)
- Works with **SSR**.
Expand Down
47 changes: 42 additions & 5 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,60 @@
module.exports = function(config) {
module.exports = function (config) {
const browsers = (process.env.KARMA_BROWSERS || "ChromeHeadless").split(",");

const testFilePattern = "tests/*.tsx";

config.set({
basePath: ".",
frameworks: ["jasmine"],
files: ["tests/dist/index.js"],
files: [
{
pattern: testFilePattern,
watched: true,
},
],
autoWatch: true,

browsers,
reporters: ["spec"],
preprocessors: {
[testFilePattern]: ["webpack", "sourcemap"],
},

// Max concurrency for SauceLabs OS plan
concurrency: 5,

client: {
jasmine: {
// Order of the tests matter, so don't randomise it
random: false
}
}
random: false,
},
},

webpack: {
mode: "development",
devtool: "inline-source-map",
module: {
rules: [
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", { loose: true, modules: false }],
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [["@babel/transform-runtime", { useESModules: true }]],
},
},
},
],
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
},
});
};
29 changes: 15 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "use-resize-observer",
"version": "6.1.0-alpha.1",
"version": "6.1.0-alpha.2",
"main": "dist/bundle.cjs.js",
"module": "dist/bundle.esm.js",
"types": "dist/index.d.ts",
Expand All @@ -10,22 +10,19 @@
"author": "Viktor Hubert <contact@hubertviktor.com>",
"license": "MIT",
"scripts": {
"build": "run-s src:build test:build",
"watch": "KARMA_BROWSERS=Chrome run-p src:watch test:watch karma:watch",
"src:build": "rollup -c && tsc && cp dist/index.d.ts polyfilled.d.ts",
"build": "rollup -c && tsc && cp dist/index.d.ts polyfilled.d.ts",
"watch": "KARMA_BROWSERS=Chrome run-p 'src:watch' 'karma:watch'",
"src:watch": "rollup -c -w",
"tsc": "tsc",
"size-limit": "size-limit",
"test": "run-s 'build' 'size-limit' 'tsc -p tests' 'test:chrome:headless' 'test:firefox:headless'",
"test:build": "parcel build tests/index.tsx --out-dir tests/dist",
"test:watch": "parcel watch tests/index.ts --out-dir tests/dist",
"check:size": "size-limit",
"check:tests": "tsc -p tests",
"test": "run-s 'build' 'check:size' 'check:tests' 'test:headless:*'",
"test:chrome": "KARMA_BROWSERS=Chrome yarn karma:run",
"test:chrome:headless": "KARMA_BROWSERS=ChromeHeadless yarn karma:run",
"test:headless:chrome": "KARMA_BROWSERS=ChromeHeadless yarn karma:run",
"test:firefox": "KARMA_BROWSERS=Firefox yarn karma:run",
"test:firefox:headless": "KARMA_BROWSERS=FirefoxHeadless yarn karma:run",
"test:headless:firefox": "KARMA_BROWSERS=FirefoxHeadless yarn karma:run",
"karma:run": "karma start --singleRun",
"karma:watch": "karma start",
"prepublish": "yarn src:build"
"prepublish": "yarn build"
},
"husky": {
"hooks": {
Expand All @@ -43,25 +40,29 @@
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/preset-env": "^7.7.7",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.9.0",
"@rollup/plugin-inject": "^4.0.1",
"@size-limit/preset-small-lib": "^4.4.5",
"@testing-library/react": "^10.0.2",
"@types/karma": "^5.0.0",
"@types/karma-jasmine": "^3.1.0",
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.6",
"babel-regenerator-runtime": "^6.5.0",
"babel-loader": "^8.1.0",
"delay": "^4.1.0",
"husky": "^4.2.5",
"karma": "^5.0.1",
"karma-chrome-launcher": "^3.0.0",
"karma-firefox-launcher": "^1.3.0",
"karma-jasmine": "^3.1.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^4.0.2",
"lint-staged": "^10.1.3",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.10.3",
"prettier": "^2.0.4",
"react": "^16.9.0",
"react-dom": "^16.9.0",
Expand Down
90 changes: 55 additions & 35 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { useEffect, useState, useRef, useMemo, RefObject } from "react";
import {
useEffect,
useState,
useRef,
useMemo,
RefObject,
MutableRefObject,
} from "react";

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

const ref = opts.ref || defaultRef;
// Saving the callback as a ref. With this, I don't need to put onResize in the
// effect dep array, and just passing in an anonymous function without memoising
// will not reinstantiate the hook's ResizeObserver
const onResize = opts.onResize;
const onResizeRef = useRef<ResizeHandler | undefined>(undefined);
onResizeRef.current = onResize;

// Using a single instance throughought the hook's lifetime
const resizeObserverRef = useRef<ResizeObserver>() as MutableRefObject<
ResizeObserver
>;
if (!resizeObserverRef.current) {
resizeObserverRef.current = new ResizeObserver((entries) => {
if (!Array.isArray(entries)) {
return;
}

// Since we only observe the one element, we don't need to loop over the
// array
if (!entries.length) {
return;
}

const entry = entries[0];

// `Math.round` is in line with how CSS resolves sub-pixel values
const newWidth = Math.round(entry.contentRect.width);
const newHeight = Math.round(entry.contentRect.height);
if (
previous.current.width !== newWidth ||
previous.current.height !== newHeight
) {
const newSize = { width: newWidth, height: newHeight };
if (onResizeRef.current) {
onResizeRef.current(newSize);
} else {
previous.current.width = newWidth;
previous.current.height = newHeight;
setSize(newSize);
}
}
});
}

const ref = opts.ref || defaultRef;
const [size, setSize] = useState<{
width?: number;
height?: number;
Expand Down Expand Up @@ -60,41 +110,11 @@ function useResizeObserver<T>(
}

const element = ref.current;
const resizeObserver = new ResizeObserver((entries) => {
if (!Array.isArray(entries)) {
return;
}

// Since we only observe the one element, we don't need to loop over the
// array
if (!entries.length) {
return;
}

const entry = entries[0];

// `Math.round` is in line with how CSS resolves sub-pixel values
const newWidth = Math.round(entry.contentRect.width);
const newHeight = Math.round(entry.contentRect.height);
if (
previous.current.width !== newWidth ||
previous.current.height !== newHeight
) {
const newSize = { width: newWidth, height: newHeight };
if (onResize) {
onResize(newSize);
} else {
previous.current.width = newWidth;
previous.current.height = newHeight;
setSize(newSize);
}
}
});

resizeObserver.observe(element);
resizeObserverRef.current.observe(element);

return () => resizeObserver.unobserve(element);
}, [ref, onResize]);
return () => resizeObserverRef.current.unobserve(element);
}, [ref]);

return useMemo(() => ({ ref, width: size.width, height: size.height }), [
ref,
Expand Down
Loading

0 comments on commit 5ab3cc9

Please sign in to comment.