Home > Mobile >  React useCallback() not working as expected
React useCallback() not working as expected

Time:05-15

I'm trying to understand useCallback() a bit better by creating a simple React app and playing around with it.

I tried wrapping handleClick function in useCallback() statement and my expectation was that ItemList component should only be re-rendered if I click the count button, but not when I change the theme. However, it's rerendering on either of those button clicks and I'm not exactly sure why.

This is my code (GitHub repo available below):

index.js:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import SebComponent from './SebComponent';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.Fragment>
    <App />
    <SebComponent />
  </React.Fragment>
);

App.js

function App() {
  return (
    <div>
      <p>This is App</p>
      {console.log("App compoent rendered")}
    </div>
  );
}

export default App;

SebComponent.js

import React, { useCallback, useState } from "react";
import ItemList from "./itemList";
import "../src/sebstyle.css"

function SebComponent(){

    console.log("rendering SebComponent...")
    const [count, setCount] = useState(1)
    const [dark, setDark] = useState(false)

    let inlineStyle = {backgroundColor: dark ? "green" : "white"}

    
    const handleClick = useCallback(() => {
        setCount(count   1)
    }, [count])


    return(
        <div className="sebComponent" style={inlineStyle}>
            <p>This is SebComponent</p>
            <button onClick={handleClick}> {count} </button>
            <br/>
            <br/>
            <button onClick={() => {setDark(x => !x)}}> change theme </button>

            <ItemList count={count}/>
        </div>
    )
}

export default SebComponent;

itemList.js:

import React from "react";

export default function ItemList(props){

    console.log("rendering item list...")
    let myArray = [];

    for (let index = 1; index < props.count; index  ) {
        myArray.push( 'item'   index);
    }
    
    console.log(myArray);

    return(
        <div>
            <p>hello</p>

            {myArray.map(
                    x => {
                        return (
                            <p key={"item"   x}> {x} </p>
                        )
                    }
                )
            }

        </div>
    )
}

sebstyle.css:

.sebComponent{
    border: 2px black solid;
    display:block;
    align-items: center;
    padding: 5px;
}

.sebComponent > button{
    margin-left: 10px;
    width: 100px;
    height: 40px;

}

I tried creating something similar to what this guy did in his video. This is my GitHub repo created to play around with this.

CodePudding user response:

If you want to skip rendering ItemList, then ItemList needs to use React.memo. This will make it so if ItemList's props have not changed, then ItemList will not rerender:

import { memo } from 'react';

function ItemList(props){
  // ...
}

export default memo(ItemList);

The only role useCallback serves in preventing rendering is to make sure that props do not change. That in turn can allow memo to do its job. But handleClick is never being passed to ItemList, so nothing is happening to item list by memomizing handleClick

CodePudding user response:

When one of those hook is invoked ( const [count, setCount] = useState(1) const [dark, setDark] = useState(false) ) React re-render all the page. You can work-around with some library as redux that let manage way more better hook. for more info read this: How to prevent re-rendering of components that have not changed?

  • Related