ummahusla / edvins-io

My digital garden
https://edvins.io
MIT License
5 stars 2 forks source link

usememo-overdose #37

Open utterances-bot opened 1 year ago

utterances-bot commented 1 year ago

useMemo overdose | Edvins Antonovs

When overuse of useMemo hook becomes a problem

https://edvins.io/usememo-overdose

shashkovdanil commented 1 year ago

I've created ESLint plugin to detect unnecessary useMemo https://github.com/shashkovdanil/eslint-plugin-usememo-recommendations

Devisione commented 1 year ago

what about if a memo is used to create a reference object and pass it as a prop? what about politics - avoid unnecessary rendering rather than doing a simple calculation?

sergeysova commented 1 year ago

Absolutely wrong way to test performance of the memo:

  const startTime = performance.now();
  const result = expensiveCalculation;
  const endTime = performance.now();
ummahusla commented 1 year ago

@shashkovdanil Nice work out there!

ummahusla commented 1 year ago

@sergeysova Hey, can you please elaborate on this? Why it's wrong?

ummahusla commented 1 year ago

@Devisione

What about if a memo is used to create a reference object and pass it as a prop?

Yup, I like this idea. I assume we are speaking about something like this idea.

import React, { useState, useMemo } from 'react';

// ChildComponent receives a reference object as a prop
const ChildComponent = ({ data }) => {
  console.log('ChildComponent rendering');
  // Use the data from the prop
  return <div>Data: {data.value}</div>;
};

// ParentComponent creates a reference object and passes it to ChildComponent
const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const referenceObject = useMemo(() => {
    return { value: count };
  }, [count]);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={handleClick}>Increment Count</button>
      <ChildComponent data={referenceObject} />
    </div>
  );
};

export default ParentComponent;

what about politics - avoid unnecessary rendering rather than doing a simple calculation?

I stay away from politics in general and bring systems instead. So, if you can agree within a team on what approach to use - stick to it.

yarastqt commented 1 year ago

@ummahusla I guess in this sample expensiveCalculation used as result of calculation but not as calling

amiiit commented 1 year ago

@ummahusla if I'm reading this correctly, this code:

  const startTime = performance.now();
  const result = expensiveCalculation;
  const endTime = performance.now();

Is measuring the time that JS takes to create a new reference in memory and assign it the value of another reference. It does not call any actual function that would do any work worth measuring. expensiveCalculation contains the result of the memo. So the result for endTime - startTime will be constant and almost zero every time.

amiiit commented 1 year ago

@ummahusla another use-case I found myself using useMemo for is in order to avoid unnecessary renders. For example if I need to convert one value to some other shape and for this I create a new object. The calculation is cheap and I don't mind creating a new object, but it will trigger a render in the lower parts of the component tree, which I want to avoid.

Would you still recommend useMemo for this, or do you have another pattern?

ummahusla commented 1 year ago

@ummahusla if I'm reading this correctly, this code:

  const startTime = performance.now();
  const result = expensiveCalculation;
  const endTime = performance.now();

Is measuring the time that JS takes to create a new reference in memory and assign it the value of another reference. It does not call any actual function that would do any work worth measuring. expensiveCalculation contains the result of the memo. So the result for endTime - startTime will be constant and almost zero every time.

Hey, thanks for your reply. I think I see where you coming form. I'm playing around with this component right now and will update the blog post at some point:

import React, { useState, useMemo } from "react";

const MemoComponent = () => {
  const [count, setCount] = useState(0);

  const expensiveCalculation = useMemo(() => {
    const startTime = performance.now();

    // Simulate an expensive calculation
    let result = 0;
    for (let i = 0; i < 10000000; i++) {
      result += i;
    }

    const endTime = performance.now();
    console.log("Expensive calculation execution time:", endTime - startTime);

    return result;
  }, []); // Include 'count' as a dependency if you want to recompute when it changes.

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={handleClick}>Increment Count</button>
      <p>Count: {count}</p>
      <p>Result: {expensiveCalculation}</p>
    </div>
  );
};

export default MemoComponent;
ummahusla commented 1 year ago

@ummahusla another use-case I found myself using useMemo for is in order to avoid unnecessary renders. For example if I need to convert one value to some other shape and for this I create a new object. The calculation is cheap and I don't mind creating a new object, but it will trigger a render in the lower parts of the component tree, which I want to avoid.

Would you still recommend useMemo for this, or do you have another pattern?

@amiiit My blog post was published on the /r/reactjs subreddit, and it was a blast. Lots of healthy discussions and related articles were posted, have a look around.

One link in particular I found interesting - https://attardi.org/why-we-memo-all-the-things/, I even emailed its author, to figure out whenever anything changed. This is his response:

Nothing changed. If anything we doubled down. I helped create a team called Client Foundations responsible for front-end code at the entire company. I personally led multiple efforts to fix performance of various React and React Native codebases and the biggest gains we’ve seen came from adding memoization where it was missing. And we also wrote custom lint rules to enforce memoization everywhere. I think the solution is for React to build a compiler to memoize things automatically so we can end this debate once and for all. I think it’s coming.