How to memoize array of selectors?
How to memoize array of selectors?
Example code:
const menu = [
{type: "home", visSelector: someSelector},
{type: "accounts", visSelector: anotherSelector}
]
const filterMenuItems = (state, menu) =>
menu.filter(i => i.visSelector(state))
const mapStateToProps = state => {
menuItems: filterMenuItems(state, menu)
}
What is the right way to memoize filterMenuItems?
visSelector are already memoized. Unfortunately, result of this filtration rerender component every time.
visSelector
Yep, i know. But how to handle such a case nicely?
– ku8ar
Jun 26 at 15:08
2 Answers
2
Provided the items are pure component, they won't all be re-rendered every time. Only the ones you're adding back after having previously removed them.
For instance, here we start out with all four children shown. If you uncheck one of the checkboxes, two of the children disappear, but no children are re-rendered. It's only if you add them back that they're re-rendered.
const Child = props => {
console.log("render", props.name);
return <div>{props.name}</div>
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.children = [
<Child key={0} name="zero" />,
<Child key={1} name="one" />,
<Child key={2} name="two" />,
<Child key={3} name="three" />
];
this.state = {
showOdds: true,
showEvens: true
};
}
evensClick = event => {
this.setState({showEvens: event.currentTarget.checked});
};
oddsClick = event => {
this.setState({showOdds: event.currentTarget.checked});
};
render() {
const {showEvens, showOdds, counter} = this.state;
return <div>
<label>
<input type="checkbox" onChange={this.evensClick} checked={showEvens} />
Show evens
</label>
<label>
<input type="checkbox" onChange={this.oddsClick} checked={showOdds} />
Show odds
</label>
<div>
{this.children.filter((child, index) => index % 2 == 0 ? showEvens : showOdds)}
</div>
</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script>
I don't think there's a way to prevent them being re-rendered when being added back.
Alternatively, you could use CSS to manager their visibility, which would prevent re-rendering them:
const Child = props => {
console.log("render", props.name);
return <div className={props.index % 2 == 0 ? "even" : "odd"}>{props.name}</div>
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.children = [
<Child index={0} key={0} name="zero" />,
<Child index={1} key={1} name="one" />,
<Child index={2} key={2} name="two" />,
<Child index={3} key={3} name="three" />
];
this.state = {
showOdds: true,
showEvens: true
};
}
evensClick = event => {
this.setState({showEvens: event.currentTarget.checked});
};
oddsClick = event => {
this.setState({showOdds: event.currentTarget.checked});
};
render() {
const {showEvens, showOdds, counter} = this.state;
const cls = (showEvens ? "" : "hide-evens ") + (showOdds ? "" : "hide-odds");
return <div>
<label>
<input type="checkbox" onChange={this.evensClick} checked={showEvens} />
Show evens
</label>
<label>
<input type="checkbox" onChange={this.oddsClick} checked={showOdds} />
Show odds
</label>
<div className={cls}>
{this.children}
</div>
</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
.hide-evens .even {
display: none;
}
.hide-odds .odd {
display: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script>
Array.filter() returns a new array on each call. You might want to try and memoize filterMenuItems based on your application requirements.
filterMenuItems
In the example below filterMenuItems, returns a new array only when state or menu arguments change:
filterMenuItems
state
menu
import { createSelector } from 'reselect';
const filterMenuItems = createSelector(
state => state,
(state, menu) => menu,
(state, menu) => menu.filter(i => i.visSelector(state))
);
And make sure you don't recreate
menu itself every time! That will also stop it from caching since it'll invalidate the inputs.– Joseph Sikorski
yesterday
menu
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
It has to, as
visSelectormakes its decision based on the whole state, which changes at every rerender– Jonas W.
Jun 26 at 15:07