Skip to content

Commit

Permalink
Efficient prop updates for resizable flex higher-order component
Browse files Browse the repository at this point in the history
Ensures we don’t propagate prop updates from the resizable flex HOC
unless something relevant has actually changed.

The main functions in the `resizableFlex` module are independent of the
current state, so we should just define them once and then merge them
into the final props returned by the component.

The props-returning function should return the same instance as
previously if the props themselves are unchanged. To do this, we want to
memoize the function so that new props are only returned if:

* The flex list (received from Redux) has changed
* The `ownProps` (passed directly to the component) have changed

In the latter case, we can’t rely on `ownProps` to be strictly equal,
however; instead, we need to create a custom memoized function that does
a shallow value check (the same kind of check used for pure
`react-redux` containers)
  • Loading branch information
outoftime committed Jun 19, 2018
1 parent 1fc84ab commit 3533c14
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 55 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -215,6 +215,7 @@
"remark-react-lowlight": "^0.7.0",
"reselect": "^3.0.1",
"scriptjs": "^2.5.8",
"shallowequal": "^1.0.2",
"slowparse": "^1.1.4",
"strip-markdown": "^3.0.0",
"stylelint": "^9.2.0",
Expand Down
121 changes: 66 additions & 55 deletions src/higherOrderComponents/resizableFlex/index.js
Expand Up @@ -4,7 +4,9 @@ import findIndex from 'lodash-es/findIndex';
import isNull from 'lodash-es/isNull';
import map from 'lodash-es/map';
import merge from 'lodash-es/merge';
import shallowequal from 'shallowequal';
import times from 'lodash-es/times';
import {createSelector, defaultMemoize} from 'reselect';

import {makeGetResizableFlexGrow} from '../../selectors';
import {updateResizableFlex} from '../../actions';
Expand All @@ -23,67 +25,76 @@ export default function resizableFlex(size) {
const regions = times(size, () => ({current: null}));
const initialMainSizes = times(size, () => null);

return (state, ownProps) => merge(
{
onResizableFlexDividerDrag(beforeIndex, event, payload) {
const afterIndex = findIndex(regions, 'current', beforeIndex + 1);
const [{current: before}, {current: after}] =
at(regions, [beforeIndex, afterIndex]);
const stateIndependentFunctions = {
onResizableFlexDividerDrag(beforeIndex, event, payload) {
const afterIndex = findIndex(regions, 'current', beforeIndex + 1);
const [{current: before}, {current: after}] =
at(regions, [beforeIndex, afterIndex]);

const {getCurrentSize, getDesiredSize} =
directionAdapterFor(before);
const {getCurrentSize, getDesiredSize} =
directionAdapterFor(before);

const [desiredBeforeFlexGrow, desiredAfterFlexGrow] =
calculateFlexGrowAfterDrag(
{
currentFlexGrow: Number(
getComputedStyle(before)['flex-grow'],
),
currentSize: getCurrentSize(before),
desiredSize: getDesiredSize(before, payload),
initialMainSize: initialMainSizes[beforeIndex],
},
{
currentFlexGrow: Number(
getComputedStyle(after)['flex-grow'],
),
currentSize: getCurrentSize(after),
initialMainSize: initialMainSizes[afterIndex],
},
);
const [desiredBeforeFlexGrow, desiredAfterFlexGrow] =
calculateFlexGrowAfterDrag(
{
currentFlexGrow: Number(
getComputedStyle(before)['flex-grow'],
),
currentSize: getCurrentSize(before),
desiredSize: getDesiredSize(before, payload),
initialMainSize: initialMainSizes[beforeIndex],
},
{
currentFlexGrow: Number(
getComputedStyle(after)['flex-grow'],
),
currentSize: getCurrentSize(after),
initialMainSize: initialMainSizes[afterIndex],
},
);

dispatch(updateResizableFlex(
instanceId,
[
{index: beforeIndex, flexGrow: desiredBeforeFlexGrow},
{index: afterIndex, flexGrow: desiredAfterFlexGrow},
],
));
},

resizableFlexRefs: map(
regions,
(region, index) => (element) => {
region.current = element;
if (isNull(element)) {
initialMainSizes[index] = null;
return;
}
dispatch(updateResizableFlex(
instanceId,
[
{index: beforeIndex, flexGrow: desiredBeforeFlexGrow},
{index: afterIndex, flexGrow: desiredAfterFlexGrow},
],
));
},

const flexGrowWas = element.style.flexGrow;
const flexShrinkWas = element.style.flexShrink;
element.style.flexGrow = element.style.flexShrink = '0';
initialMainSizes[index] = directionAdapterFor(element).
getCurrentSize(element);
element.style.flexGrow = flexGrowWas;
element.style.flexShrink = flexShrinkWas;
},
),
resizableFlexRefs: map(
regions,
(region, index) => (element) => {
region.current = element;
if (isNull(element)) {
initialMainSizes[index] = null;
return;
}

resizableFlexGrow: getResizableFlexGrow(state),
},
const flexGrowWas = element.style.flexGrow;
const flexShrinkWas = element.style.flexShrink;
element.style.flexGrow = element.style.flexShrink = '0';
initialMainSizes[index] = directionAdapterFor(element).
getCurrentSize(element);
element.style.flexGrow = flexGrowWas;
element.style.flexShrink = flexShrinkWas;
},
),
};

ownProps,
return createSelector(
[
getResizableFlexGrow,
defaultMemoize(
(_state, ownProps) => ownProps,
shallowequal,
),
],
(resizableFlexGrow, ownProps) => merge(
{resizableFlexGrow},
ownProps,
stateIndependentFunctions,
),
);
},
{
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Expand Up @@ -9321,6 +9321,10 @@ shallow-copy@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170"

shallowequal@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.0.2.tgz#1561dbdefb8c01408100319085764da3fcf83f8f"

shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
Expand Down

0 comments on commit 3533c14

Please sign in to comment.