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.





It has to, as visSelector makes its decision based on the whole state, which changes at every rerender
– Jonas W.
Jun 26 at 15:07


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.

Popular posts from this blog

How to input without newline? (Python)

C++ thread error: no type named ‘type’ MINGW

Analog for TagView in flutter