Skip to content

Commit b5edd2d

Browse files
committed
add new options interface, linting, support for observing individual dimensions (note: still testing/iterating these changes)
1 parent f3dce69 commit b5edd2d

File tree

2 files changed

+109
-55
lines changed

2 files changed

+109
-55
lines changed

src/index.d.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
declare module 'use-viewport-sizes' {
2+
export type VPSizesHasher = (({ vpW: number, vpH: number }) => String);
3+
export type VPSizesOptions ={
4+
debounceTimeout?: number,
5+
throttleTimeout?: number,
6+
hasher?: VPSizesHasher,
7+
dimension?: 'w'|'h'|'both' = 'both'
8+
};
9+
210
/**
311
* Hook which observes viewport dimensions. Returns [width, height] of
4-
* current visible viewport of app.
5-
*
6-
* If no input specified, returns the [width, height] when the window changes.
7-
*
8-
* If input is specified as a number, it interprets this as the number of
9-
* miliseconds to debounce before updates.
10-
*
11-
* If the input is specified as a function, it accepts a callback
12-
* with the viewport width and height passed in the first arg as
13-
* { vpW, vpH }, and will only return a new value and update when
14-
* the hash-value returned changes.
12+
* current visible viewport of app, or the specific dimension
1513
*/
1614
export default function(
17-
input:number | (({ vpW: number, vpH: number }) => String)
18-
):[vpW: number, vpH: number];
15+
input:number | VPSizesHasher | VPSizesOptions
16+
):[vpW: number, vpH: number] | number;
1917
}

src/index.js

Lines changed: 98 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { useState, useRef, useLayoutEffect, useCallback, useMemo } from 'react';
1+
import { useState, useMemo, useCallback, useRef, useLayoutEffect, } from 'react';
22

3-
function getVpWidth () {
3+
function getVpWidth() {
44
return (typeof window != 'undefined') ? Math.max(
55
window.document.documentElement.clientWidth,
66
window.innerWidth || 0
7-
) : 0;
7+
) : 0;
88
}
99

1010

11-
function getVpHeight () {
11+
function getVpHeight() {
1212
return (typeof window != 'undefined') ? Math.max(
1313
window.document.documentElement.clientHeight,
1414
window.innerHeight || 0
@@ -19,15 +19,12 @@ function getVpHeight () {
1919
// Shared State //
2020
// =============== //
2121

22-
// NOTE: using vars and separating since Babel
23-
// transpilation saves a bit of filesize here
24-
2522
/**
2623
* listening component functions
2724
*
2825
* @type Function
2926
*/
30-
var listeners = new Set();
27+
const listeners = new Set();
3128

3229
/**
3330
* contains a "hasher" which manages the behavior
@@ -36,12 +33,16 @@ var listeners = new Set();
3633
* for minimum overhead and so we can reference
3734
* it easily on deletion
3835
*
39-
* @type Map<Object, {{ hasher: Function, prevHash: Any }}>
36+
* @type Map<Object, {{
37+
* hasher: Function,
38+
* prevHash: Any,
39+
* options
40+
* }}>
4041
*/
41-
var resolverMap = new Map();
42+
const resolverMap = new Map();
4243

43-
var vpWidth = getVpWidth();
44-
var vpHeight = getVpHeight();
44+
let vpWidth = getVpWidth();
45+
let vpHeight = getVpHeight();
4546

4647
// should only be called by *one* component once;
4748
// will iterate through all subscribers
@@ -51,20 +52,26 @@ function onResize() {
5152
vpWidth = getVpWidth();
5253
vpHeight = getVpHeight();
5354

54-
listeners.forEach(function(listener) {
55+
listeners.forEach(listener => {
5556
const params = { vpW: vpWidth, vpH: vpHeight };
5657

57-
if(!resolverMap.has(listener)) {
58-
listener(params);
58+
let shouldRun = false;
59+
let hash;
60+
61+
const { options, prevHash=undefined } = resolverMap?.get(listener) || {};
62+
const { hasher } = options;
63+
64+
if(!options?.hasher) {
65+
shouldRun = true;
5966
}
6067
else {
61-
const { hasher, prevHash } = resolverMap.get(listener);
62-
const hash = hasher(params);
68+
hash = hasher(params);
69+
if(hash != prevHash) { shouldRun = true }
70+
}
6371

64-
if(hash != prevHash) {
65-
listener({ ...params, hash });
66-
resolverMap.set(listener, { hasher, prevHash: hash });
67-
}
72+
if(shouldRun) {
73+
resolverMap.set(listener, { options, prevHash: hash });
74+
listener({ ...params, options, hash });
6875
}
6976
});
7077
}
@@ -85,24 +92,50 @@ function onResize() {
8592
* @input {Function|Number} input
8693
*/
8794
function useViewportSizes(input) {
88-
const hasCustomHasher = (typeof input == 'function');
89-
const [state, setState] = useState(()=> !hasCustomHasher ?
90-
({ vpW: vpWidth, vpH: vpHeight }) :
91-
(input && input({ vpW: vpWidth, vpH:vpHeight }))
92-
);
93-
const timeout = useRef(undefined);
94-
const listener = useCallback(
95-
(!input || hasCustomHasher) ?
96-
state => setState(state) :
97-
state => {
98-
if(timeout.current) { clearTimeout(timeout.current) }
99-
timeout.current = setTimeout(() => setState(state), input);
100-
}, [input]);
95+
const hasher = ((typeof input == 'function') ?
96+
input :
97+
input?.hasher
98+
) || undefined;
99+
100+
const debounceTimeout = input?.debounceTimeout || undefined;
101+
102+
const throttleTimeout = ((typeof input == 'number') ?
103+
input :
104+
input?.throttleTimeout
105+
) || undefined;
106+
107+
const dimension = input?.dimension || 'both';
108+
109+
const options = {
110+
...(typeof input == 'function' ? {} : input),
111+
dimension,
112+
hasher
113+
};
114+
115+
const [state, setState] = useState(() => (!hasher ?
116+
{ vpW: vpWidth, vpH: vpHeight } :
117+
hasher && hasher({ vpW: vpWidth, vpH: vpHeight })
118+
));
119+
const debounceTimeoutRef = useRef(undefined);
120+
const listener = useCallback((!hasher ?
121+
state => setState(state) :
122+
state => {
123+
if(debounceTimeoutRef.current) {
124+
clearTimeout(debounceTimeoutRef.current);
125+
}
101126

102-
useLayoutEffect(() => {
103-
if(hasCustomHasher) {
104-
resolverMap.set(listener, { hasher: input, prevHash: state.hash });
127+
debounceTimeoutRef.current = setTimeout(() => {
128+
setState(state);
129+
}, debounceTimeoutRef);
105130
}
131+
), [debounceTimeoutRef, hasher, dimension]);
132+
133+
useLayoutEffect(() => {
134+
resolverMap.set(listener, {
135+
options,
136+
prevHash: state.hash || undefined
137+
});
138+
106139
listeners.add(listener);
107140

108141
if(window && listeners.size == 1) {
@@ -117,16 +150,39 @@ function useViewportSizes(input) {
117150
listeners.delete(listener);
118151

119152
if(listeners.size == 0) {
120-
window.removeEventListener('resize', onResize);
153+
window?.removeEventListener?.('resize', onResize);
121154
}
122155
};
123156
}, [listener]);
124157

125-
const returnValue = useMemo(() => ([
126-
state.vpW, state.vpH, hasCustomHasher ? state.hash : onResize
127-
]), [state, onResize])
158+
let dimensionHash;
159+
160+
switch (dimension) {
161+
default:
162+
case 'both': {
163+
dimensionHash = `${state?.vpW}_${state.vpH}`;
164+
break;
165+
}
166+
case 'w': {
167+
dimensionHash = state?.vpW || 0;
168+
break;
169+
}
170+
case 'h': {
171+
dimensionHash = state?.vpH || 0;
172+
break;
173+
}
174+
}
175+
176+
const returnValue = useMemo(() => {
177+
switch (dimension) {
178+
default:
179+
case 'both': { return [state?.vpW || 0, state?.vpH || 0] }
180+
case 'w': { return state?.vpW || 0 }
181+
case 'h': { return state?.vpH || 0 }
182+
}
183+
}, [dimensionHash, onResize, dimension]);
128184

129185
return returnValue;
130186
}
131187

132-
export default useViewportSizes
188+
export default useViewportSizes;

0 commit comments

Comments
 (0)