Dmitry Reutov
Dmitry Reutov

Reputation: 3032

Logic of rerendering in react function components

I have a simple App component

export default function App() {
  const [count, changeCount] = useState(0)
  const onIncreaseClick = useCallback(() => {
    changeCount(count + 1)
  }, [count]) 

  const onPress = useCallback(() => {
    alert('pressed')
  }, [])

  return (<>
    <button onClick={onIncreaseClick}>Increase</button>
    <ButtonPressMe onClick={onPress} />
  </>);
}

I expect that onPress variable contains always the same link since parameters never change

And i expect that my ButtonPressMe component will be rendered just once - with the first App component rendering... because it has just one prop and value of this prop never change... therefore no need to rerender component. Correct?

Inside my ButtonPressMe component i check it with console.log

const ButtonPressMe = ({ onClick }) => {
  console.log('Button press Me render')
  return <button onClick={onClick}>Press me</button>
}

And against my expectations it rerenders each time when parent component rerenders after Increase button is pressed.

Did i misunderstood something?

sandbox to check

Upvotes: 1

Views: 83

Answers (3)

Yousaf
Yousaf

Reputation: 29282

By default, when a parent component re-renders, all of its child components re-render too.

useCallback hook will preserve the identity of the onPress function but that won't prevent a re-render of the ButtonPressMe component. To prevent a re-render, React.memo() is used. useCallback hook is used to avoid passing a new reference to a function, as a prop to a child component, each time a parent component re-renders.

In your case, combination of React.memo and useCallback hook will prevent a re-render of ButtonPressMe component.

function App() {
  const [count, changeCount] = React.useState(0);
  const onIncreaseClick = React.useCallback(() => {
    changeCount(count + 1);
  }, [count]);

  const onPress = React.useCallback(() => {
    alert("pressed");
  }, []);

  return (
    <div>
      <button onClick={onIncreaseClick}>Increase</button>
      <ButtonPressMe onClick={onPress} />
    </div>
  );
}

const ButtonPressMe = React.memo(({ onClick }) => {
  console.log("Button press Me render");
  return <button onClick={onClick}>Press me</button>;
});

ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 1

PhyDev
PhyDev

Reputation: 73

The default behavior in React is to change everything in the App when anything changes, in your case you're changing the state of the parent of your custom button, therefore React re-renders everything including your button.

You can find an explanation on how React decides to re-render components here: https://lucybain.com/blog/2017/react-js-when-to-rerender/#:~:text=A%20re%2Drender%20can%20only,should%20re%2Drender%20the%20component.

Upvotes: 0

Nicholas Tower
Nicholas Tower

Reputation: 85032

And against my expectations it rerenders each time when parent component rerenders after Increase button is pressed.

Did i misunderstood something?

That's the default behavior in react: when a component renders, all of its children render too. If you want the component to compare its old and new props and skip rendering if they didn't change, you need to add React.memo to the child:

const ButtonPressMe = React.memo(({ onClick }) => {
  return <button onClick={onClick}>Press me</button>
})

Upvotes: 1

Related Questions