Open panzerdp opened 4 years ago
with your "Good Use Case" doesn't the item in onItemClick
return a SyntheticBaseEvent rather than the item?
SyntheticBaseEvent {_reactName: "onClick", _targetInst: null, type: "click", nativeEvent: MouseEvent, target: div.css-18shr4g,
wouldn't you need to use
const map = item => <div onClick={() => onItemClick(item)}>{item}</div>;
?
^^^ in which case afaik it won't be memoized because () => onItemClick(item)
actually creates a unique function each time and fails comparison?
with your "Good Use Case" doesn't the item in
onItemClick
return a SyntheticBaseEvent rather than the item?
SyntheticBaseEvent {_reactName: "onClick", _targetInst: null, type: "click", nativeEvent: MouseEvent, target: div.css-18shr4g,
wouldn't you need to use
const map = item => <div onClick={() => onItemClick(item)}>{item}</div>;
?
Good catch @codemonkeynorth. You can access the item’s element from the event: event.currentTarget
.
Either way, even using <div onClick={() => onItemClick(item)}>{item}</div>
as you suggest:
import React from 'react';
import useSearch from './fetch-items';
function MyBigList({ term, onItemClick }) {
const items = useSearch(term);
const map = item => <div onClick={() => onItemClick(item)}>{item}</div>;
return <div>{items.map(map)}</div>;
}
export default React.memo(MyBigList);
React.memo()
is going to prevent re-rendering of the entire MyBigList
as long as onItemClick
is the same function object.
Hi, if you use currentTarget
I think you can't access the item itself, but you can access the element. Therefore If you want to match an id
etc to an array you may want to use eg data-id={id}
on the element and read event.currentTarget.dataset.id
however you're right that () => onItemClick(item)
does not break memoization
I've put a demo here: https://codesandbox.io/s/competent-violet-uk2nh?file=/src/MyBigList.js
I was a little thrown because the custom hook "useWhyDidYouUpdate" includes the onItemClick
in it's output, but essentially that function only got recreated because term
changed
thanks for the clarification.
Awesome!
Awesome, nicely explained
Awesome, nicely explained
Thanks @VinayRajput!
Firstly, Thank you so much for sharing such a deep understanding!
Here is my code sandbox: https://codesandbox.io/s/fancy-fast-092me and the question I'm having is written under "renderDropdown()" function of child component.
This is related to your Bad use case,
Let's assume in child component, I'm sorting 1000 items and binding a dropdown. Now if it get's re-rendered, It will sort those items on every re-render. Please suggest the best way to optimize that?
Thanks for the great explanation about useCallback! But I'm a little confused about why you added 'term' to dependency array in "A good use case" even though the callback function don't use 'term' variable.
Thanks for the great explanation about useCallback! But I'm a little confused about why you added 'term' to dependency array in "A good use case" even though the callback function don't use 'term' variable.
It was made so that a new callback function is created only when term
prop changes, to avoid breaking the memoization of the big list.
Great article, looking forward to getting to know your thoughts about useMemo hook.
Great article, looking forward to getting to know your thoughts about useMemo hook.
Thanks! Good idea to write a post about useMemo()
.
Amazing explanation! Simple and well didactic!
Amazing explanation! Simple and well didactic!
Thanks @ecatugy!
what a good post, tks
This is the best blog series I have came across. Very well written. Thanks a lot.
This is the best blog series I have came across. Very well written. Thanks a lot.
Glad you like it @vinodf2f!
Золотые слова. Так и нахлынули воспоминания о тех дивных вечерах, когда я зачитывался творениями Мартина Фаулера =)
Hey @panzerdp , in the list use case, I have to get the row item returned onClick
. If we don't wrap the onClick
handler in useCallback, then on every user action, the row will start re-rendering. I have 50 such elements in the table with 8 columns.
I think my case a good use case for using useCallBack
with deps
being the item
. Am I wrong here?
Hey @panzerdp , in the list use case, I have to get the row item returned
onClick
. If we don't wrap theonClick
handler in useCallback, then on every user action, the row will start re-rendering. I have 50 such elements in the table with 8 columns.I think my case a good use case for using
useCallBack
withdeps
being theitem
. Am I wrong here?
Hard to say exactly. Can you provide a demo or code samples?
const StudentNameRenderer = (props) => {
// onTableAction is a function provided by parent StudentsList component
const { onTableAction, student } = props;
// I think we need to memoize this based on the student for better perf
const onClick = () => {
onTableAction({
type: SHOW_STUDENT_DETAILS,
payload: student,
});
}
return <div onClick={onClick}>{student.displayName}</div>;
}
@panzerdp , here StudentNameRenderer
is the renderer of one of the columns in a table with max 50 items.
const StudentNameRenderer = (props) => { // onTableAction is a function provided by parent StudentsList component const { onTableAction, student } = props; // I think we need to memoize this based on the student for better perf const onClick = () => { onTableAction({ type: SHOW_STUDENT_DETAILS, payload: student, }); } return <div onClick={onClick}>{student.displayName}</div>; }
@panzerdp , here
StudentNameRenderer
is the renderer of one of the columns in a table with max 50 items.
In a table of max 50 items that have only 1 div
, I wouldn't bother with memoization at all.
This was awesome, man. I don't usually like article for picking up on something, but the official docs for this were really vague, but you manage to explain it crystal clear!
This was awesome, man. I don't usually like article for picking up on something, but the official docs for this were really vague, but you manage to explain it crystal clear!
Thanks @ccaalluumm, glad you like it.
what's difference useMemo vs useCallback
@alisher-usmonov I'm not sure why you got the reaction you did, rather than someone helping to answer your question. Many people have this same question at first.
The main difference is that useMemo
is used for values, and useCallback
is used for functions.
Here's a contrived example:
const someValue = useMemo(() => someLongList.reduce((acc, item) => acc + item.value), [someLongList]);
Here we have someLongList
that we need to sum up values for. We don't want to run this on each render since the list is long and that would make this an expensive calculation. With useMemo
, we can memoize the value, and only re-calculate when someLongList
changes
For useCallback
const someHandlerFunction = useCallback(() => { ... }, []);
Here we have a function we've memoized. See the original article to determine when is the right time to use useCallback
or not
I think this article gives questionable advice. Profiling costs development time and is not a trivial task (performance can vary wildly depending on user data). Recognizing you have a performance issue can cost maintenance time. Developers tend to work and debug on more powerful hardware than what the actual users will use. Will your QA catch it? Will your users report it, or will these performance issues cause you to lose users?
It's all optimization: when choosing not to use useCallback, you're choosing to prioritize some things over others too. The things I mentioned should factor in as well.
I doubt useCallback's performance hit is worth thinking about. And deps are trivial to maintain with the exhaustive-deps lint rule. Even if you don't have CI, you can do CI=true
in the .env.production
file so that npm run build
would fail on warnings such as exhaustive-deps.
Good article in that it makes you consider these things, but I don't agree with the summary.
Good article in that it makes you consider these things, but I don't agree with the summary.
No problem! Thanks for sharing your opinion @VsevolodGolovanov.
In the opening remarks of the post you say:
Such usage of useCallback() without profiling makes the component slower.
Later in the post it seems like you get around to explaining that point by summarizing:
In conclusion, the optimization costs more than not having the optimization.
Did you have any evidence of useCallback
being called being slower than redefining the inline function on each render? I suppose it'd probably depend on the complexity of the equality check in useCallback
's deps but I'm curious if your conclusion is that it's "slower" because of the maintainability concerns you raise or if it's "slower" based on real profiling metrics.
Well explained.
If we need to create polyfill for this how to create? Can you make a post on this?
Fabulous article!. But for every example please share the full repository link. so that we can run that and get to know easily.
Thank you very much for taking the time to write these articles, they have been very helpful.
Thank you very much for taking the time to write these articles, they have been very helpful.
You're welcome @agustinven!
Thx @panzerdp,Great,very practical and deep.
Your articles are always easy to follow, great work Dmitri!
Even useCallback() returning the same function object, still, the inline function is re-created on every re-rendering (useCallback() just skips it).
this is cofusing to read, still re-created vs. useCallback skip it
so I think you want to say that useCallback
's equality check is expensive than not use it
also increase some runtime code
this is cofusing to read, still re-created vs. useCallback skip it
@HomyeeKing I've made some updates to the post. Hopefully now it's easier to read.
Thank you for your sharing!
@panzerdp Nice! Well articulated. Keep writing such tricky and important concepts about JavaScript world in a simple way.
@panzerdp Nice! Well articulated. Keep writing such tricky and important concepts about JavaScript world in a simple way.
Will do! 😁
I am new to React hooks and functional components (been in the Web Components world), but I am confused on the difference between the two examples.
The click handlers seem to the same to me. Is it because the one is a custom onItemClick
function that it doesn't change?
Or is it because the handleClick
from the bad use case is being passed to a child?
...
const onItemClick = useCallback(event => {
console.log('You clicked ', event.currentTarget);
}, [term]);
return (
<MyBigList
term={term}
onItemClick={onItemClick}
/>
);
}
...
const handleClick = useCallback(() => {
// handle the click event
}, []);
return <MyChild onClick={handleClick} />;
}
function MyChild ({ onClick }) {
return <button onClick={onClick}>I am a child</button>;
}
Written on 05/02/2020 11:14:26
URL: https://dmitripavlutin.com/react-usecallback/