Skip to content

Commit c5cf44a

Browse files
authored
Merge pull request #264 from maslianok/v12.1.0
V12.1.0
2 parents 598736a + 7c585e3 commit c5cf44a

File tree

9 files changed

+578
-344
lines changed

9 files changed

+578
-344
lines changed

README.md

Lines changed: 146 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ Modern browsers now have native support for detecting element size changes throu
1616

1717
No `window.resize` listeners! No timeouts!
1818

19-
## Is it necessary for you to use this library?
19+
## Should you use this library?
2020

21-
Container queries now work in [all major browsers](https://caniuse.com/css-container-queries). It's very likely you can solve your task using [pure CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries).
21+
**Consider CSS Container Queries first!** They now work in [all major browsers](https://caniuse.com/css-container-queries) and might solve your use case with pure CSS.
2222

23-
<details><summary>Example</summary>
23+
<details><summary>CSS Container Queries Example</summary>
2424

2525
```html
2626
<div class="post">
@@ -51,106 +51,210 @@ Container queries now work in [all major browsers](https://caniuse.com/css-conta
5151

5252
</details>
5353

54+
**Use this library when you need:**
55+
56+
- JavaScript-based resize logic with full TypeScript support
57+
- Complex calculations based on dimensions
58+
- Integration with React state/effects
59+
- Programmatic control over resize behavior
60+
5461
## Installation
5562

56-
```ssh
57-
npm i react-resize-detector
58-
// OR
63+
```bash
64+
npm install react-resize-detector
65+
# OR
5966
yarn add react-resize-detector
67+
# OR
68+
pnpm add react-resize-detector
6069
```
6170

62-
## Example
71+
## Quick Start
6372

64-
```jsx
73+
### Basic Usage
74+
75+
```tsx
6576
import { useResizeDetector } from 'react-resize-detector';
6677

6778
const CustomComponent = () => {
68-
const { width, height, ref } = useResizeDetector();
79+
const { width, height, ref } = useResizeDetector<HTMLDivElement>();
6980
return <div ref={ref}>{`${width}x${height}`}</div>;
7081
};
7182
```
7283

73-
#### With props
84+
### With Resize Callback
7485

75-
```js
76-
import { useResizeDetector } from 'react-resize-detector';
86+
```tsx
87+
import { useCallback } from 'react';
88+
import { useResizeDetector, OnResizeCallback } from 'react-resize-detector';
7789

7890
const CustomComponent = () => {
79-
const onResize = useCallback(() => {
80-
// on resize logic
91+
const onResize: OnResizeCallback = useCallback((payload) => {
92+
if (payload.width !== null && payload.height !== null) {
93+
console.log('Dimensions:', payload.width, payload.height);
94+
} else {
95+
console.log('Element unmounted');
96+
}
8197
}, []);
8298

83-
const { width, height, ref } = useResizeDetector({
84-
handleHeight: false,
85-
refreshMode: 'debounce',
86-
refreshRate: 1000,
99+
const { width, height, ref } = useResizeDetector<HTMLDivElement>({
87100
onResize,
88101
});
89102

90103
return <div ref={ref}>{`${width}x${height}`}</div>;
91104
};
92105
```
93106

94-
#### With custom ref
107+
### With External Ref (Advanced)
95108

96109
_It's not advised to use this approach, as dynamically mounting and unmounting the observed element could lead to unexpected behavior._
97110

98-
```js
111+
```tsx
112+
import { useRef } from 'react';
99113
import { useResizeDetector } from 'react-resize-detector';
100114

101115
const CustomComponent = () => {
102-
const targetRef = useRef();
116+
const targetRef = useRef<HTMLDivElement>(null);
103117
const { width, height } = useResizeDetector({ targetRef });
104118
return <div ref={targetRef}>{`${width}x${height}`}</div>;
105119
};
106120
```
107121

108-
## API
122+
## API Reference
123+
124+
### Hook Signature
125+
126+
```typescript
127+
useResizeDetector<T extends HTMLElement = HTMLElement>(
128+
props?: useResizeDetectorProps<T>
129+
): UseResizeDetectorReturn<T>
130+
```
131+
132+
### Props
133+
134+
| Prop | Type | Description | Default |
135+
| ----------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------- |
136+
| `onResize` | `(payload: ResizePayload) => void` | Callback invoked with resize information | `undefined` |
137+
| `handleWidth` | `boolean` | Trigger updates on width changes | `true` |
138+
| `handleHeight` | `boolean` | Trigger updates on height changes | `true` |
139+
| `skipOnMount` | `boolean` | Skip the first resize event when component mounts | `false` |
140+
| `refreshMode` | `'throttle' \| 'debounce'` | Rate limiting strategy. See [lodash docs](https://lodash.com/docs) | `undefined` |
141+
| `refreshRate` | `number` | Delay in milliseconds for rate limiting | `1000` |
142+
| `refreshOptions` | `{ leading?: boolean; trailing?: boolean }` | Additional options for throttle/debounce | `undefined` |
143+
| `observerOptions` | `ResizeObserverOptions` | Options passed to [`resizeObserver.observe`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/observe) | `undefined` |
144+
| `targetRef` | `MutableRefObject<T \| null>` | External ref to observe (use with caution) | `undefined` |
109145

110-
| Prop | Type | Description | Default |
111-
| --------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
112-
| onResize | Func | Function that will be invoked with `width`, `height` and ResizeObserver `entry` arguments | `undefined` |
113-
| handleWidth | Bool | Trigger `onResize` on width change | `true` |
114-
| handleHeight | Bool | Trigger `onResize` on height change | `true` |
115-
| skipOnMount | Bool | Do not trigger onResize when a component mounts | `false` |
116-
| refreshMode | String | Possible values: `throttle` and `debounce` See [lodash docs](https://lodash.com/docs#debounce) for more information. `undefined` - callback will be fired for every frame | `undefined` |
117-
| refreshRate | Number | Use this in conjunction with `refreshMode`. Important! It's a numeric prop so set it accordingly, e.g. `refreshRate={500}` | `1000` |
118-
| refreshOptions | Object | Use this in conjunction with `refreshMode`. An object in shape of `{ leading: bool, trailing: bool }`. Please refer to [lodash's docs](https://lodash.com/docs/4.17.11#throttle) for more info | `undefined` |
119-
| observerOptions | Object | These options will be used as a second parameter of [`resizeObserver.observe`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/observe) method. | `undefined` |
120-
| targetRef | Ref | Use this prop to pass a reference to the element you want to attach resize handlers to. It must be an instance of `React.useRef` or `React.createRef` functions | `undefined` |
146+
## Advanced Examples
121147

122-
## Testing with Enzyme and Jest
148+
### Responsive Component
123149

124-
Thanks to [@Primajin](https://github.com/Primajin) for posting this [snippet](https://github.com/maslianok/react-resize-detector/issues/145)
150+
```jsx
151+
import { useResizeDetector } from 'react-resize-detector';
152+
153+
const ResponsiveCard = () => {
154+
const { width, ref } = useResizeDetector();
155+
156+
const cardStyle = {
157+
padding: width > 600 ? '2rem' : '1rem',
158+
fontSize: width > 400 ? '1.2em' : '1em',
159+
flexDirection: width > 500 ? 'row' : 'column',
160+
};
161+
162+
return (
163+
<div ref={ref} style={cardStyle}>
164+
<h2>Responsive Card</h2>
165+
<p>Width: {width}px</p>
166+
</div>
167+
);
168+
};
169+
```
170+
171+
### Chart Resizing
172+
173+
```jsx
174+
import { useResizeDetector } from 'react-resize-detector';
175+
import { useEffect, useRef } from 'react';
176+
177+
const Chart = () => {
178+
const chartRef = useRef(null);
179+
const { width, height, ref } = useResizeDetector({
180+
refreshMode: 'debounce',
181+
refreshRate: 100,
182+
});
183+
184+
useEffect(() => {
185+
if (width && height && chartRef.current) {
186+
// Redraw chart with new dimensions
187+
redrawChart(chartRef.current, width, height);
188+
}
189+
}, [width, height]);
190+
191+
return <canvas ref={ref} />;
192+
};
193+
```
194+
195+
### Performance Optimization
196+
197+
```jsx
198+
import { useResizeDetector } from 'react-resize-detector';
199+
200+
const OptimizedComponent = () => {
201+
const { width, height, ref } = useResizeDetector({
202+
// Only track width changes
203+
handleHeight: false,
204+
// Debounce rapid changes
205+
refreshMode: 'debounce',
206+
refreshRate: 150,
207+
// Skip initial mount calculation
208+
skipOnMount: true,
209+
// Use border-box for more accurate measurements
210+
observerOptions: { box: 'border-box' },
211+
});
212+
213+
return <div ref={ref}>Optimized: {width}px wide</div>;
214+
};
215+
```
216+
217+
## Browser Support
218+
219+
- ✅ Chrome 64+
220+
- ✅ Firefox 69+
221+
- ✅ Safari 13.1+
222+
- ✅ Edge 79+
223+
224+
For older browsers, consider using a [ResizeObserver polyfill](https://github.com/que-etc/resize-observer-polyfill).
225+
226+
## Testing
125227

126228
```jsx
127229
const { ResizeObserver } = window;
128230

129231
beforeEach(() => {
130232
delete window.ResizeObserver;
233+
// Mock ResizeObserver for tests
131234
window.ResizeObserver = jest.fn().mockImplementation(() => ({
132235
observe: jest.fn(),
133236
unobserve: jest.fn(),
134237
disconnect: jest.fn(),
135238
}));
136-
137-
wrapper = mount(<MyComponent />);
138239
});
139240

140241
afterEach(() => {
141242
window.ResizeObserver = ResizeObserver;
142243
jest.restoreAllMocks();
143244
});
144-
145-
it('should do my test', () => {
146-
// [...]
147-
});
148245
```
149246

247+
## Performance Tips
248+
249+
1. **Use `handleWidth`/`handleHeight: false`** if you only need one dimension
250+
2. **Enable `skipOnMount: true`** if you don't need initial measurements
251+
3. **Use `debounce` or `throttle`** for expensive resize handlers
252+
4. **Specify `observerOptions.box`** for consistent measurements
253+
150254
## License
151255

152256
MIT
153257

154-
## ❤️
258+
## ❤️ Support
155259

156260
Show us some love and STAR ⭐ the project if you find it useful

package.json

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-resize-detector",
3-
"version": "12.0.2",
3+
"version": "12.1.0",
44
"description": "React resize detector",
55
"type": "module",
66
"exports": "./build/index.js",
@@ -25,21 +25,22 @@
2525
"lodash": "^4.17.21"
2626
},
2727
"devDependencies": {
28-
"@rollup/plugin-commonjs": "^28.0.2",
29-
"@rollup/plugin-node-resolve": "^16.0.0",
28+
"@eslint/js": "^9.28.0",
29+
"@rollup/plugin-commonjs": "^28.0.3",
30+
"@rollup/plugin-node-resolve": "^16.0.1",
3031
"@rollup/plugin-typescript": "^12.1.2",
31-
"@types/lodash": "^4.17.13",
32-
"@types/react": "^19.0.2",
33-
"@types/react-dom": "^19.0.2",
34-
"eslint": "^9.17.0",
35-
"eslint-config-prettier": "^9.1.0",
36-
"eslint-plugin-react": "^7.37.3",
37-
"prettier": "^3.4.2",
38-
"rollup": "^4.29.1",
32+
"@types/lodash": "^4.17.17",
33+
"@types/react": "^19.1.7",
34+
"@types/react-dom": "^19.1.6",
35+
"eslint": "^9.28.0",
36+
"eslint-config-prettier": "^10.1.5",
37+
"eslint-plugin-react": "^7.37.5",
38+
"prettier": "^3.5.3",
39+
"rollup": "^4.42.0",
3940
"rollup-plugin-node-externals": "^8.0.0",
4041
"tslib": "^2.8.1",
41-
"typescript": "^5.7.2",
42-
"typescript-eslint": "^8.19.0"
42+
"typescript": "^5.8.3",
43+
"typescript-eslint": "^8.34.0"
4344
},
4445
"peerDependencies": {
4546
"react": "^18.0.0 || ^19.0.0"

playground/src/Sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
} from '@radix-ui/themes';
2121
import { Github, MessageCircleQuestion, Rocket, WandSparkles } from 'lucide-react';
2222

23-
import { Box, ResfreshModeType, useDemoContext } from './context';
23+
import { Box, RefreshModeType, useDemoContext } from './context';
2424
import { Snippet } from './Snippet';
2525

2626
export const Sidebar = () => {
@@ -244,7 +244,7 @@ export const Sidebar = () => {
244244

245245
<RadioCards.Root
246246
value={refreshMode || ''}
247-
onValueChange={(v) => setRefreshMode((v || undefined) as ResfreshModeType)}
247+
onValueChange={(v) => setRefreshMode((v || undefined) as RefreshModeType)}
248248
columns="3"
249249
>
250250
<RadioCards.Item value="">No Throttle (Default)</RadioCards.Item>

playground/src/context.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { createContext, useContext, useMemo, useState } from 'react';
22

3-
export type ResfreshModeType = 'throttle' | 'debounce' | undefined;
3+
export type RefreshModeType = 'throttle' | 'debounce' | undefined;
44

55
export type Box = ResizeObserverBoxOptions | undefined;
66

77
export interface DemoContext {
88
box: Box;
99
setBox: React.Dispatch<React.SetStateAction<Box>>;
1010

11-
refreshMode: ResfreshModeType;
12-
setRefreshMode: React.Dispatch<React.SetStateAction<ResfreshModeType>>;
11+
refreshMode: RefreshModeType;
12+
setRefreshMode: React.Dispatch<React.SetStateAction<RefreshModeType>>;
1313

1414
isLoading: boolean;
1515
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
@@ -29,7 +29,7 @@ export const useDemoContext = () => useContext(DemoContext);
2929

3030
export const DemoProvider = ({ children }: { children: React.ReactNode }) => {
3131
const [box, setBox] = useState<Box>(undefined);
32-
const [refreshMode, setRefreshMode] = useState<ResfreshModeType>(undefined);
32+
const [refreshMode, setRefreshMode] = useState<RefreshModeType>(undefined);
3333
const [isLoading, setIsLoading] = useState(false);
3434
const [handleHeight, setHandleHeight] = useState(true);
3535
const [handleWidth, setHandleWidth] = useState(true);

0 commit comments

Comments
 (0)