https://codesandbox.io/s/busy-paper-w39kvw?file=/src/components/Comments.js:141-200
const initialState = {
comments: [
{
id: 1,
username: "Kevin",
date: "3 hours ago",
text: "Hello",
votes: 12,
upvoted: false,
downvoted: false,
comments: []
}
]
}
In my comments code I have a useSelector
const { comments } = useSelector(state => state.comments)
this renders all the comments everytime a new comment is added
as shown here using react dev tools add on to highlight when a component renders: https://i.gyazo.com/43a93b6d07a5802d91d9f68684e5ded5.mp4
I tried to use React.memo
to memoize the comment but im not sure why that didn't work. Is there anyway to stop rendering all the comments when a comment gets added?
CodePudding user response:
When a component is wrapped in React.memo(), React renders the component and memoizes the result. Before the next render, if the new props are the same, React reuses the memoized result skipping the next rendering.
In your code below, you are passing the allComments
function to the comment component.
const allComments = (comments, bg) => {
const output = comments.map((comment) => {
return (
<Comment
key={comment.id}
comment={comment}
allComments={allComments} // the function get's passed as new function object on each rerender
bg={bg}
/>
);
});
return output;
};
what is the problem then and why this behavior?
because of the function equality check, functions in javascript are treated as firstclass citizens, in other word, functions are objects.
function factory() {
return (a, b) => a b;
}
const sum1 = factory();
const sum2 = factory();
sum1(1, 2); // => 3
sum2(1, 2); // => 3
sum1 === sum2; // => false
sum1 === sum1; // => true
The functions sum1 and sum2 share the same code source but they are different function objects. Comparing them sum1 === sum2 evaluates to false. this is how Javascript works.
In your code, a new function object allComments
gets created in each render by react, which is eventually gets passed as a new prop to the React.memo()
. By default React.memo()
does a shallow comparison of props and objects of props. which is why it triggers the a new rerender.
Now we understand deeply what was the problem and what causes this behavior.
The solution is to wrap your allComments
with useCallback
What is the purpose of useCallback? it maintain a single function instance between renderings. and thus React.memo()
will work.
const allComments = useCallback((comments, bg) => {
const output = comments.map((comment) => {
return (
<Comment
key={comment.id}
comment={comment}
allComments={allComments}
bg={bg}
/>
);
});
return output;
},[]);