Skip to content

Commit f3ec0da

Browse files
committed
remove global listener
1 parent 01eebdb commit f3ec0da

File tree

3 files changed

+33
-63
lines changed

3 files changed

+33
-63
lines changed

README.md

+5-17
Original file line numberDiff line numberDiff line change
@@ -169,23 +169,6 @@ const mapSizesToProps = sizes => ({
169169
```
170170
> `sizes` argument is an object with `width` and `height` properties and represents DOM window width and height.
171171
172-
## Performance Notes
173-
174-
#### Global listener
175-
Window resize event listener is grouped at one global listener only.
176-
So you can have as many components as you want using Sizes hoc, which will not have multiple resize listeners,
177-
but only one that will dispatch for all instances.
178-
179-
Don't worry, Sizes handles `componentWillUnmount` to remove unnecessary callbacks.
180-
When each component unmounted, it unsubscribe for global dispatches, and when last component is unmounted,
181-
the listener is removed.
182-
183-
#### Throttle
184-
By now the listener callback is hardcoded as [throttle](https://css-tricks.com/debouncing-throttling-explained-examples/)
185-
function of 200ms. Because of having a global listener, we have a limitation on changing this behavior.
186-
I don't see it as tradeoff, and I think it can have good workarounds.
187-
Then, for the future, I intend to work to bring a solution to this important customization.
188-
189172
## Guide
190173

191174
#### mapSizesToProps(sizes)
@@ -215,6 +198,11 @@ const mapSizesToProps = ({ width }) => ({
215198
});
216199
```
217200

201+
## Performance Notes
202+
203+
#### Shallow Compare
204+
React Sizes do a shallow compare in props generated from `mapSizesToProps` (called `propsToPass`), so it will only rerender when they really change. If you create a deep data sctructure, this can generate false positives. In these cases, we recommend using immutable for a more reliable shallow compare result. Or just don't use deep data structures, if possible.
205+
218206
## Contribute
219207

220208
You can help improving this project sending PRs and helping with issues.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"lint": "eslint src",
99
"build": "cross-env NODE_ENV=production npm run lint && npm run clear && babel src -d lib --source-maps",
1010
"prepublish": "npm run lint && npm run clear && npm run build",
11-
"storybook": "start-storybook -p 6006",
11+
"storybook": "cross-env NODE_ENV=debug start-storybook -p 6006",
1212
"build-storybook": "build-storybook"
1313
},
1414
"repository": {

src/withSizes.js

+27-45
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
/* eslint-disable no-console */
22

33
import React, { Component } from 'react'
4-
import { v4 } from 'uuid'
5-
import keys from 'lodash.keys'
64
import throttle from 'lodash.throttle'
75

86
import * as presets from './presets'
@@ -11,67 +9,51 @@ import getDisplayName from './utils/getDisplayName'
119
import shallowDiff from './utils/shallowDiff'
1210
import getWindowSizes from './utils/getWindowSizes'
1311

14-
const debug = process.env.NODE_ENV !== 'production'
15-
16-
let resizeListener
17-
const listeners = {}
12+
const debug = process && process.env &&
13+
process.env.NODE_ENV === 'debug'
1814

1915
const withSizes = (...mappedSizesToProps) => (WrappedComponent) => {
20-
const parseMappedSizesToProps = (dimensions, props) => {
21-
const propsToPass = mappedSizesToProps
16+
const parseMappedSizesToProps = (dimensions, props) =>
17+
mappedSizesToProps
2218
.map(check => check(dimensions, props))
23-
.reduce((acc, props) => ({...acc, ...props}), {})
24-
25-
return propsToPass
26-
}
19+
.reduce((acc, props) => ({ ...acc, ...props }), {})
2720

28-
return class extends Component {
29-
static displayName = `withSizes(${getDisplayName(WrappedComponent)})`;
21+
return class ComponentWithSizes extends Component {
22+
static displayName = `withSizes(${getDisplayName(WrappedComponent)})`
3023

3124
state = {
32-
id: `A${v4()}`,
25+
initialSizes: getWindowSizes(window),
3326
propsToPass: parseMappedSizesToProps(getWindowSizes(window), this.props),
34-
};
35-
36-
componentDidMount() {
37-
if (!resizeListener) {
38-
resizeListener = window.addEventListener('resize', this.throttledWindowResize)
39-
}
40-
41-
listeners[this.state.id] = this.listenerCallback
42-
43-
this.dispatchSizes()
4427
}
4528

46-
componentWillUnmount() {
47-
delete listeners[this.state.id]
48-
if (keys(listeners).length < 1) {
49-
window.removeEventListener('resize', this.throttledWindowResize)
50-
resizeListener = null
51-
}
52-
}
29+
/* Dispatching & Throttling */
5330

54-
listenerCallback = (sizes) => {
55-
const propsToPass = parseMappedSizesToProps(sizes, this.props)
31+
dispatchSizes = () => {
32+
const propsToPass = parseMappedSizesToProps(getWindowSizes(window), this.props)
5633

5734
if (shallowDiff(propsToPass, this.state.propsToPass)) {
5835
this.setState({ propsToPass })
5936
}
6037
}
6138

62-
dispatchSizes = () => {
63-
keys(listeners).forEach(key => {
64-
const callback = listeners[key]
39+
throttledDispatchSizes = (
40+
throttle(this.dispatchSizes, 200)
41+
)
6542

66-
if (typeof callback === 'function') {
67-
callback(getWindowSizes(window))
68-
}
69-
})
70-
};
43+
/* Lifecycles */
7144

72-
throttledWindowResize = (
73-
throttle(this.dispatchSizes, 200)
74-
);
45+
componentDidMount() {
46+
window.addEventListener('resize', this.throttledDispatchSizes)
47+
48+
/* dispatch if aren't computed on first render */
49+
if (!this.state.initialSizes.canUseDOM) {
50+
this.dispatchSizes()
51+
}
52+
}
53+
54+
componentWillUnmount() {
55+
window.removeEventListener('resize', this.throttledDispatchSizes)
56+
}
7557

7658
render() {
7759
if (debug) console.log('render', this.state.propsToPass)

0 commit comments

Comments
 (0)