2

I saw many article said that define inline function in render function in react can cause to performance issue.
Therefore they recommend to define the function outside of the render function and use it where i need (onClick etc).
I built a sample code that i have a list of button and each button will increase the state by the index in the list, But its throw error.
How i can pass parameter and not use inline function in onClick

const App = () => {
  const [number, setNumber] = useState(1);
  const increaseNumber = (num) => {
    setNumber((prevState) => prevState + num);
  };
  return (
    <div>
      {[...Array(5)].map((item, index) => (
        <button key={index} onClick={increaseNumber(index)}>
          {`increase by ${index}`}
        </button>
      ))}
      <div>{number}</div>
    </div>
  );
};
export default App;
7
  • 1
    Can you share some of those articles you read? Commented Dec 24, 2021 at 22:06
  • "How i can pass parameter and not use inline function in onClick" You can't. Because the function depends on the .map callback variable and is therefore different for every element of the array, the function has to be created (directly or indirectly) in the .map callback. Commented Dec 24, 2021 at 22:08
  • @Spankied This is one of the articles codementor.io/blog/… Commented Dec 24, 2021 at 22:10
  • 1
    the function is generated on every render. I think u should use useCallback to define it only on mount. EDIT: for this function is microptimization, but if you looping to an array with 1000 entities it could cost some time Commented Dec 24, 2021 at 22:15
  • Did you try the code suggested in the article? Commented Dec 24, 2021 at 22:16

3 Answers 3

2

I'll preface my answer by saying you really should profile your application and identify specific performance issues before trying to optimize anything. In this case, you could avoid creating a new callback with each map iteration by using data attributes.

function App() {
    const [number, setNumber] = useState(1);

    const increaseNumber = (event) => {
        const index = parseInt(event.target.dataset.index);
        setNumber((prevState) => prevState + index);
    };

    return (
        <div>
            {[...Array(5)].map((item, index) => (
                <button key={index} onClick={increaseNumber} data-index={index}>
                    {`increase by ${index}`}
                </button>
            ))}
            <div>{number}</div>
        </div>
    );
}

export default App;

Typically the only time you would care about creating a new callback per render is when the callback is used as a prop in a child component. Using something like useCallback can help to avoid unnecessary child renders in those cases.

Sign up to request clarification or add additional context in comments.

8 Comments

Still defining onclick fn in the render function, but this does answer the question
@spankied I think you cannot really avoid the inline onClick definition unless you want to query the DOM and then add event listeners manually—that is an even worse option. I think the gist is to not define a new function in onClick (ie an arrow function) but simply pass a memorized function reference or an unchanging function reference (this question offered the latter) is sufficient for a performance gain.
@spankied There is no need to move the function outside the component definition. Yes it does have a slight performance gain but you harp on the fact that is created in a render function. It is not. The component returns the render function.
Nice way, but what to do if i need to pass an object to function? Split the object ?
@Terry Theres also the issue with setState not being defined outside the component. But ya your response somewhat makes sense. But since JSX compiles to React.createElement() does that mean this is createElement() is a render fn.
|
2

Aproach 1: useMemo

When the arguments are fixed like it's your case, you may use useMemo:

import { useMemo, useState } from "react";

const indexes = [...Array(5)].map((_item, idx) => idx);

const App = () => {
    const [number, setNumber] = useState(1);

    const increaseNumber = useMemo(() => {
        return indexes.map(index => () => setNumber(prevNumber => prevNumber + index));
    }, [indexes]);

    return (
        <div>
            {indexes.map(index => (
                <button key={index} onClick={increaseNumber[index]}>
                    increase by {index}
                </button>
            ))}

            <div>{number}</div>
        </div>
    );
};

Approach 2: wraper component + useCallback

Create your own button component and pass the index:

const IncreaseButton = ({ setNumber, index }) => {
    const increaseByIndex = useCallback(() => {
        return setNumber(prevValue => prevValue + index);
    }, [setNumber, index]);

    return <button onClick={increaseByIndex}>increase by {index}</button>;
};

2 Comments

Shouldn’t index be passed to the dependency array for useMemo?
@Terry No, index is used within the block. indexes, on the other hand, could be passed. However, as it's defined in the root level, linters would say just to remove it. It could be added for clarity, though.
0

You can pass an item as a function that had been memoized to the onClick prop of react button elements.

const App = () => {
  const [number, setNumber] = useState(1);
  const increaseNumber = (num) => () => {
    setNumber((prevState) => prevState + num);
  };

  const btns = useMemo(() => {
    // here I am using lodash memoize function you may use your own
    let inc = _.memoize(increaseNumber)
    return Array(500).fill(0).map((_, index) => inc(index))
  }, [])
  return (
    <div>
      {btns.map((item, index) => (
        <button key={index} onClick={item}>
          {`increase by ${index}`}
        </button>
      ))}
      <div>{number}</div>
    </div>
  );
};

4 Comments

The question is: "How i can pass parameter and not use inline function in onClick"
IncreaseNumber is still defined inside the render fn. Also adding an anonymous function on the onClick handler might not make much difference.
This causes an error
updated, I tested it by more than 500 buttons without any performance issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.