20

Suppose I have these React components:

const Compo1 = ({theName}) => {
  return (
    <Nested foo={() => console.log('Dr. ' + theName)}/>
  );
};

const Compo2 = ({theName}) => {
  function theFoo() {
    console.log('Dr. ' + theName);
  }
  return (
    <Nested foo={theFoo}/>
  );
};

And the nested component, wrapped in memo:

const Nested = React.memo(({foo}) => {
  return (
    <Button onClick={foo}>Click me</Button>
  );
});

Function passed in foo is always recreated in Compo1 and also Compo2, correct?

If so, since foo receives a new function every time, does it mean memo will be useless, thus Nested will always be re-rendered?

rodrigocfd
  • 6,450
  • 6
  • 34
  • 68

3 Answers3

17

You can use new hooks Api(React >= 16.8) to avoid recreating callback func.

Just using useCallback hook for this.

For e.g

Parent component

import React, { useCallback} from 'react';

const ParentComponent = ({theName}) => {
  const theFoo = () => {
    console.log('Dr. ' + theName);
  }

  const memoizedCallback = useCallback(theFoo , []);

  return (
     <Nested foo={memoizedCallback}/>
   );
};

useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed (which passed in the second argument) In this case we pass empty array as dependencies and therefore the function will be created only once.

And nested component:

import React, { memo } from 'react';

const Nested = ({foo}) => (
  <Button onClick={foo}>Click me</Button>
);

export default memo(Nested);

For more info - https://reactjs.org/docs/hooks-reference.html#usecallback

Emi
  • 4,597
  • 2
  • 31
  • 34
Artur Khrabrov
  • 743
  • 8
  • 11
  • 1
    It seems to me you should be passing `theName` in the dependency array then. Otherwise if `theName` changes in your parent component, the `onClick` listener will still have the old value – Geoffrey H Sep 06 '19 at 11:42
1

The memo function will shallow compare every different props, including functions. However, by redefining your function inside your components in every render, you will create a different reference every time, triggering a re-render.

Although, as your can see in Comp3, you can still use memo and avoid re-rendering by declaring an external function :

class App extends React.Component {
    constructor(props) {
        super(props)

        this.state = {

        }
    }

    componentDidMount = () => {
        setInterval(() => { this.setState({ e: true }) }, 2000)
    }

    render() {
        return (
            <div>
                <Compo1 />
                <Compo2 />
                <Compo3 />
            </div>
        )
    }
}

const Compo1 = () => <Nested foo={() => console.log('Comp1 rendering')} />

const Compo2 = () => {
    function theFoo() {
        console.log('Comp2 rendering');
    }
    return <Nested foo={theFoo} />
};

const foo3 = function (text) { console.log('Comp3 rendering ' + text) }

const Compo3 = () => <Nested foo={foo3} />

const Nested = React.memo(({ foo }) => {
    foo('a param')
    return <div />
})


ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'>
Treycos
  • 7,373
  • 3
  • 24
  • 47
0

The nicest way I found was to use useRef. Specifically I use this structure together with Formik to prevent re-rendering of a long list of input values.

const MyMemoizedGroupList = React.memo(
  ({
    groups,
    arrayHelpersRef,
  }) => {
    // [..] stuff
  }
}

function MainComponent() {
  const groupsArrayHelpersRef = React.useRef();

  return (
    <Formik 
      // [..] stuff
      render={({ values }) => (
        <Form>
          <FieldArray
            name="groups"
            render={arrayHelpers => {
              groupsArrayHelpersRef.current = arrayHelpers;
            }}
          />
          <MyMemoizedGroupList 
            groups={values.groups} 
            arrayHelpersRef={groupsArrayHelpersRef} 
          /> 
          {/* [..] stuff */}
        </Form>
      )} 
    />
  );
}
Alex
  • 1,689
  • 18
  • 27