Skip to content

updates: optimize dimension hashing ~50x & reduce build size by half #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
{
"presets": [
"@babel/preset-env"
[
"@babel/preset-env",
{
"targets": {
"browsers": "last 2 versions, not ie 11",
"node": "20"
},
"exclude": [
"@babel/plugin-proposal-optional-chaining",
"transform-destructuring",
"transform-typeof-symbol",
"transform-parameters",
"transform-for-of",
"transform-regenerator",
"transform-object-rest-spread"
]
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": false
}
]
]
}
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
![Github Workflow Status](https://github.com/rob2d/use-viewport-sizes/actions/workflows/node.js.yml/badge.svg)
[![NPM](https://img.shields.io/npm/l/use-viewport-sizes.svg)](https://github.com/rob2d/use-viewport-sizes/blob/master/LICENSE)

a tiny TS-compatible React hook which allows you to track visible window viewport size in your components w/ an optional debounce, throttle or custom memo function for updates for optimal rendering.
A lightweight, TypeScript-compatible React hook for tracking viewport sizes in your components. Includes optional debounce, throttle, and custom memoization for optimized rendering.

## Table of Contents
- [Installation](#installation)
- [Benefits](#benefits)
- [Usage](#usage)
- [Basic Use-case](#basic-use-case)
- [Measure/Update only on one dimension](#measureupdate-only-on-one-dimension)
- [With Throttling](#with-throttling)
- [With Debouncing](#with-debouncing)
- [Only update vpW/vpH passed on specific conditions](#only-update-vpwvph-passed-on-specific-conditions)
- [Support](#support)
- [License](#license)

## Installation ##

Expand All @@ -13,7 +25,7 @@ npm install -D use-viewport-sizes
```

## Benefits ##
- extremely lightweight and zero dependencies -- adds **2.04kb** after gzip.
- extremely lightweight and zero dependencies -- adds **2.38kb** pre gzip, and **1.09kb** after gzip.
- only one `window.onresize` handler used to subscribe to any changes in an unlimited number of components no matter the use-cases.
- optional debounce to delay updates until user stops dragging their window for a moment; this can make expensive components with size-dependent calculations run much faster and your app feel smoother.
- debouncing does not create new handlers or waste re-renders in your component; the results are also pooled from only one resize result.
Expand Down Expand Up @@ -125,4 +137,4 @@ Otherwise, if this was useful and you'd like to show your support, no donations

## License ##

- Open Source **[MIT license](http://opensource.org/licenses/mit-license.php)**
Open Source **[MIT license](http://opensource.org/licenses/mit-license.php)**
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "use-viewport-sizes",
"version": "0.7.3",
"version": "0.8.0",
"description": "a tiny TS-compatible React hook which allows you to track visible window viewport size in your components w/ an optional debounce, throttle or custom memo function for updates for optimal rendering.",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"scripts": {
"start": "webpack --watch --mode development",
"build": "webpack --mode production",
"build:babel": "babel src --out-dir build --extensions \".js\"",
"dev": "webpack-dev-server --env testServer --mode development --open",
"prepublishOnly": "npm run build",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
Expand Down
60 changes: 25 additions & 35 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import {
} from 'react';

function getVpWidth() {
return (typeof window != 'undefined') ? Math.max(
window.document.documentElement.clientWidth,
window.innerWidth || 0
) : 0;
return Math.max(
globalThis?.document?.documentElement?.clientWidth || 0,
globalThis?.innerWidth || 0
);
}


function getVpHeight() {
return (typeof window != 'undefined') ? Math.max(
window.document.documentElement.clientHeight,
window.innerHeight || 0
) : 0;
return Math.max(
globalThis?.document?.documentElement?.clientHeight || 0,
globalThis?.innerHeight || 0
);
}

// Avoid useLayoutEffect warning during SSR
Expand Down Expand Up @@ -68,40 +68,35 @@ let vpHeight = getVpHeight();
*/
function triggerResizeListener(listener, vpWidth, vpHeight) {
const params = { vpW: vpWidth, vpH: vpHeight };

let shouldRun = false;
let hash;

const { options, prevHash=undefined } = resolverMap?.get(listener) || {};
const { hasher } = options;

if(!hasher) {
if(!options.hasher) {
switch (options?.dimension) {
case 'w':
hash = `${vpWidth}`;
hash = vpWidth;
break;
case 'h':
hash = `${vpHeight}`;
hash = vpHeight;
break;
default:
case 'both':
hash = `${vpWidth}_${vpHeight}`;
hash = (vpWidth << 16) | vpHeight;
break;
}
}
else {
hash = hasher(params);
hash = options.hasher(params);
}

if(hash != prevHash) { shouldRun = true }

if(shouldRun) {
if(hash != prevHash) {
const state = { ...params, options, hash };
resolverMap.set(listener, {
options,
prevHash: hash,
prevState: state
});

listener(state);
}
}
Expand All @@ -126,13 +121,6 @@ function onResize() {
// the Hook //
// =============== //

function getInitialState(options, vpW, vpH) {
return (!options.hasher ?
{ vpW, vpH } :
options.hasher({ vpW: vpWidth, vpH: vpHeight })
)
}

export default function useViewportSizes(input) {
const hasher = ((typeof input == 'function') ?
input :
Expand All @@ -156,7 +144,10 @@ export default function useViewportSizes(input) {
hasher
};

const [state, setState] = useState(() => getInitialState(options));
const [state, setState] = useState(() => {
const defaultState = { vpW: vpWidth, vpH: vpHeight };
return options.hasher ? options.hasher(defaultState) : defaultState;
});
const debounceTimeoutRef = useRef(undefined);
const throttleTimeoutRef = useRef(undefined);
const lastThrottledRef = useRef(undefined);
Expand Down Expand Up @@ -247,25 +238,24 @@ export default function useViewportSizes(input) {
switch (dimension) {
default:
case 'both': {
dimensionHash = `${state?.vpW}_${state.vpH}`;
dimensionHash = `${state.vpW}_${state.vpH}`;
break;
}
case 'w': {
dimensionHash = state?.vpW || 0;
dimensionHash = state.vpW || 0;
break;
}
case 'h': {
dimensionHash = state?.vpH || 0;
dimensionHash = state.vpH || 0;
break;
}
}

const returnValue = useMemo(() => {
switch (dimension) {
default:
case 'both': { return [state?.vpW || 0, state?.vpH || 0, onResize] }
case 'w': { return [state?.vpW || 0, onResize] }
case 'h': { return [state?.vpH || 0, onResize] }
default: { return [state.vpW || 0, state.vpH || 0, onResize] }
case 'w': { return [state.vpW || 0, onResize] }
case 'h': { return [state.vpH || 0, onResize] }
}
}, [dimensionHash, onResize, dimension]);

Expand Down
2 changes: 1 addition & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = {
include: path.resolve(__dirname, 'src'),
exclude: /(node_modules|bower_components|build)/,
use: {
loader: 'babel-loader'
loader: 'babel-loader',
}
}
]
Expand Down