Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit fc85cd1

Browse files
maclockardFezVrasta
authored andcommitted
feat: allow popper modfiers to be updated (#302)
* Allow popper modfiers to be updated Currently if a consumer updates the `modifiers` object prop, unless another prop also changes, the new modifiers are not applied. For example, if the passed modifier `flip.enabled` were to change from `true` to `false`, the popper doesn't pick up on that fact and continues allowing the popover contents to flip. This PR allows for the `Popper` component to construct a new popper instance with the new modifiers if and only if the `modifiers` object changes (note: this is a reference comparison and not a value comparison). I'm not that familiar with Popper.JS so if there is a more efficient way to make sure modifier changes get propagated to the underlying popover, please let me know! As it stands, this _could_ cause some unnecessary popper instance creation/destruction if the consumer isn't properly memoizing the `modifiers` object they pass. This could be partially solved by doing something like shallow comparing each modifier option object one by one instead of a top level reference comparison. Alternatively, if there is a more performant way to imperatively update an existing popper instance, that may be better to use to avoid as much churn. In fact, it would be even possible to combine both of these solutions as well. * Added development warning if not memoizing * fix tests * Extracted shallow equals to a utility * Updated based on feedback
1 parent ada724a commit fc85cd1

File tree

4 files changed

+61
-22
lines changed

4 files changed

+61
-22
lines changed

.size-snapshot.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
{
22
"dist/index.umd.js": {
3-
"bundled": 65802,
4-
"minified": 23248,
5-
"gzipped": 7012
3+
"bundled": 66802,
4+
"minified": 23724,
5+
"gzipped": 7169
66
},
77
"dist/index.umd.min.js": {
8-
"bundled": 31389,
9-
"minified": 12810,
10-
"gzipped": 4174
8+
"bundled": 31438,
9+
"minified": 12846,
10+
"gzipped": 4182
1111
},
1212
"dist/index.esm.js": {
13-
"bundled": 11275,
14-
"minified": 6748,
15-
"gzipped": 1854,
13+
"bundled": 12271,
14+
"minified": 7288,
15+
"gzipped": 2066,
1616
"treeshaked": {
1717
"rollup": {
18-
"code": 3718,
18+
"code": 3754,
1919
"import_statements": 137
2020
},
2121
"webpack": {
22-
"code": 4824
22+
"code": 4860
2323
}
2424
}
2525
}

demo/index.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ const modifiers = {
5353
hide: { enabled: false },
5454
};
5555

56+
const animatedModifiers = {
57+
...modifiers,
58+
// We disable the built-in gpuAcceleration so that
59+
// Popper.js will return us easy to interpolate values
60+
// (top, left instead of transform: translate3d)
61+
// We'll then use these values to generate the needed
62+
// css tranform values blended with the react-spring values
63+
computeStyle: { gpuAcceleration: false },
64+
}
65+
5666
const Demo = enhance(
5767
({ activePlacement, setActivePlacement, isPopper2Open, togglePopper2 }) => (
5868
<Fragment>
@@ -121,15 +131,7 @@ const Demo = enhance(
121131
show ? ({ rotation, scale, opacity, top: topOffset }) => (
122132
<Popper
123133
placement="bottom"
124-
modifiers={{
125-
...modifiers,
126-
// We disable the built-in gpuAcceleration so that
127-
// Popper.js will return us easy to interpolate values
128-
// (top, left instead of transform: translate3d)
129-
// We'll then use these values to generate the needed
130-
// css tranform values blended with the react-spring values
131-
computeStyle: { gpuAcceleration: false },
132-
}}
134+
modifiers={animatedModifiers}
133135
>
134136
{({
135137
ref,

src/Popper.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import PopperJS, {
99
} from 'popper.js';
1010
import type { Style } from 'typed-styles';
1111
import { ManagerContext } from './Manager';
12-
import { safeInvoke, unwrapArray } from './utils';
12+
import { safeInvoke, unwrapArray, shallowEqual } from './utils';
1313

1414
type getRefFn = (?HTMLElement) => void;
1515
type ReferenceElement = ReferenceObject | HTMLElement | null;
@@ -164,8 +164,22 @@ export class InnerPopper extends React.Component<PopperProps, PopperState> {
164164
if (
165165
this.props.placement !== prevProps.placement ||
166166
this.props.referenceElement !== prevProps.referenceElement ||
167-
this.props.positionFixed !== prevProps.positionFixed
167+
this.props.positionFixed !== prevProps.positionFixed ||
168+
this.props.modifiers !== prevProps.modifiers
168169
) {
170+
171+
// develop only check that modifiers isn't being updated needlessly
172+
if (process.env.NODE_ENV === "development") {
173+
if (
174+
this.props.modifiers !== prevProps.modifiers &&
175+
this.props.modifiers != null &&
176+
prevProps.modifiers != null &&
177+
shallowEqual(this.props.modifiers, prevProps.modifiers)
178+
) {
179+
console.warn("'modifiers' prop reference updated even though all values appear the same.\nConsider memoizing the 'modifiers' object to avoid needless rendering.");
180+
}
181+
}
182+
169183
this.updatePopperInstance();
170184
} else if (
171185
this.props.eventsEnabled !== prevProps.eventsEnabled &&

src/utils.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,26 @@ export const safeInvoke = (fn: ?Function, ...args: *) => {
1515
return fn(...args);
1616
}
1717
}
18+
19+
/**
20+
* Does a shallow equality check of two objects by comparing the reference
21+
* equality of each value.
22+
*/
23+
export const shallowEqual = (objA: { [key: string]: any}, objB: { [key: string]: any}): boolean => {
24+
var aKeys = Object.keys(objA);
25+
var bKeys = Object.keys(objB);
26+
27+
if (bKeys.length !== aKeys.length) {
28+
return false;
29+
}
30+
31+
for (var i = 0; i < bKeys.length; i++) {
32+
var key = aKeys[i];
33+
34+
if (objA[key] !== objB[key]) {
35+
return false;
36+
}
37+
}
38+
39+
return true;
40+
}

0 commit comments

Comments
 (0)