panzerdp / dmitripavlutin.com-comments

7 stars 0 forks source link

/react-usecallback #67

Open panzerdp opened 4 years ago

panzerdp commented 4 years ago

Written on 05/02/2020 11:14:26

URL: https://dmitripavlutin.com/react-usecallback/

codemonkeynorth commented 3 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>; ?

codemonkeynorth commented 3 years ago

^^^ in which case afaik it won't be memoized because () => onItemClick(item) actually creates a unique function each time and fails comparison?

panzerdp commented 3 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>; ?

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.

codemonkeynorth commented 3 years ago

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.

image

peraltafederico commented 3 years ago

Awesome!

VinayRajput commented 3 years ago

Awesome, nicely explained

panzerdp commented 3 years ago

Awesome, nicely explained

Thanks @VinayRajput!

asananddevsingh commented 3 years ago

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?

YimJiYoung commented 3 years ago

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.

panzerdp commented 3 years ago

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.

Gleb-Gaiduk commented 3 years ago

Great article, looking forward to getting to know your thoughts about useMemo hook.

panzerdp commented 3 years ago

Great article, looking forward to getting to know your thoughts about useMemo hook.

Thanks! Good idea to write a post about useMemo().

ecatugy commented 3 years ago

Amazing explanation! Simple and well didactic!

panzerdp commented 3 years ago

Amazing explanation! Simple and well didactic!

Thanks @ecatugy!

wuhonglei commented 3 years ago

what a good post, tks

vinodf2f commented 3 years ago

This is the best blog series I have came across. Very well written. Thanks a lot.

panzerdp commented 3 years ago

This is the best blog series I have came across. Very well written. Thanks a lot.

Glad you like it @vinodf2f!

dandgerson commented 3 years ago

Золотые слова. Так и нахлынули воспоминания о тех дивных вечерах, когда я зачитывался творениями Мартина Фаулера =)

RajuSuranagi commented 3 years ago

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?

panzerdp commented 3 years ago

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?

Hard to say exactly. Can you provide a demo or code samples?

RajuSuranagi commented 3 years ago
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.

panzerdp commented 3 years ago
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.

ccaalluumm commented 3 years ago

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!

panzerdp commented 3 years ago

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.

zhondori commented 3 years ago

what's difference useMemo vs useCallback

jonrose-dev commented 2 years ago

@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

VsevolodGolovanov commented 2 years ago

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.

panzerdp commented 2 years ago

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.

aedryan commented 2 years ago

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.

mashish584 commented 2 years ago

Well explained.

abhishekdubey1 commented 2 years ago

If we need to create polyfill for this how to create? Can you make a post on this?

ajwebdev commented 2 years ago

Fabulous article!. But for every example please share the full repository link. so that we can run that and get to know easily.

agustinven commented 2 years ago

Thank you very much for taking the time to write these articles, they have been very helpful.

panzerdp commented 2 years ago

Thank you very much for taking the time to write these articles, they have been very helpful.

You're welcome @agustinven!

codthing commented 2 years ago

Thx @panzerdp,Great,very practical and deep.

drayilara commented 2 years ago

Your articles are always easy to follow, great work Dmitri!

HomyeeKing commented 2 years ago

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

panzerdp commented 2 years ago

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.

AndyFang36 commented 2 years ago

Thank you for your sharing!

chandrashelar commented 1 year ago

@panzerdp Nice! Well articulated. Keep writing such tricky and important concepts about JavaScript world in a simple way.

panzerdp commented 1 year ago

@panzerdp Nice! Well articulated. Keep writing such tricky and important concepts about JavaScript world in a simple way.

Will do! 😁

SethDonohue commented 1 year ago

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>;
}