2

I´m learning React and i found that React.memo() "is not working", because my component again re-render on every update that i do on the parent class-based component. But the problem is that props on component don´t change, at least it make sense for me

I used useEffect hook to print on my screen that re-render, although i use React.memo(Men)

const Men = props => {
        useEffect(() => {
          console.log("rendered");
        });
        return <p onClick={props.add}>{props.adder}</p>;
    };
    React.memo(Men);

    class App extends React.Component {
        state = {
          counter: 0,
          adder: "press"
        };

        add = () => {
          this.setState(prevState => {
            return {
              counter: prevState.counter + 1
            };
          });
        };

        render() {
          return (
            <div className="App">
              <p>{this.state.counter}</p>
              <Men adder={this.state.adder} add={this.add} />
            </div>
          );
        }
      }

I expect that in my console the message 'rendered' inside the useEffect hook appears only once time.

SAUL ROJAS
  • 45
  • 1
  • 6

2 Answers2

1

React hooks also take a dependency array, without it the useEffect hook will fire its effect on every render. react hooks reference

useEffect(() => {
  console.log("rendered");
});  // every render

useEffect(() => {
  console.log("rendered");
}, []);  // on mount, once

useEffect(() => {
  console.log("rendered");
}, [propValue]);  // every time propValue is a new reference

The memo HOC function can also take an equality comparison function to further test when re-renders should happen, but note that react still controls when they do happen. HOCs (Higher Order Components) wrap a component and return a new component to be rendered. You wrap your component but do not save the returned value to render later.

const MemoizedComponent = React.memo(Component, [optional prop compare function]);
...
render() {
  return (
    <MemoizedComponent />
  );
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • That is the point, though. That the `Men` component should not be re-rendered on each click....therefore, `useEffect` should not be firing on each click, but it is...due to `memo` not being used correctly.. – Matt Oestreich Jul 31 '19 at 23:27
  • 1
    Ah yes, OP is also misusing memo HOC. Question is a little unclear in this regard. – Drew Reese Jul 31 '19 at 23:39
  • OP is using the HOC, just not correctly. OP needs to use the return value from `React.memo(Men)` instead of just using the HOC and trying to use the same component. In this scenario `React.memo(Men)` is essentially doing nothing. Normally this isn't an issue because you'd be doing: `export default React.memo(Men)`, but in this scenario you have to use the return from `React.memo(Men)` See my answer which contains 2 CodePens showing this. This is not an issue with how `useEffect` is being used. https://stackoverflow.com/a/57299948/10431732 – Matt Oestreich Jul 31 '19 at 23:47
  • Ok, I am not the one confused about how hooks or `memo` work. Why are you trying to explain my answer to me? My answer also helps explain why their usage of `useEffect` also will fire on every render which was an unexpected result observed by the OP. – Drew Reese Jul 31 '19 at 23:55
  • Because `useEffect` is irrelevant in this context. `useEffect` *will* fire on every render, ***if not properly memoized***. When using `memo` correctly, in OP's code, `useEffect` does not fire on each click, only because `memo` is preventing the re-render. Therefore, `useEffect` is being fired due to improper use of `memo`, not due to how `useEffect` works, nor is `useEffect` causing this behavior, nor is it 'unexpected'.. It is a good explanation, but it is irrelevant in this context. Just trying to help..... Cheers! – Matt Oestreich Aug 01 '19 at 00:02
1

This is happening due to how you are using memo - you need to use the return value that React.memo(Men) gives you.

Like this:

This CodePen will cause a re-render

This CodePen will NOT cause a re-render

Correct:

const MenBefore = props => {
  React.useEffect(() => {
    console.log("rendered");
  });
  return <p onClick={props.add}>{props.adder}</p>;
};
////////////////////////////////////////
const Men = React.memo(MenBefore); // <--- THIS LINE
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

class App extends React.Component {
  state = {
    counter: 0,
    adder: "Click Me - I will -NOT- cause a re-render"
  };

  add = _ => {
    this.setState(prevState => {
      return {
        counter: prevState.counter + 1
      };
    });
  };

  render() {
    return (
      <div className="App">
        <p>{this.state.counter}</p>
        <Men adder={this.state.adder} add={this.add} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.body);

Incorrect:

const Men = props => {
  React.useEffect(() => {
    console.log("rendered");
  });
  return <p onClick={props.add}>{props.adder}</p>;
};
/////////////////////////////
React.memo(Men); // <<<--------- WRONG
// ^^^^^^^^^^^^^^^^^^^^^^^^^^

class App extends React.Component {
  state = {
    counter: 0,
    adder: "Click Me - I will cause a re-render"
  };

  add = _ => {
    this.setState(prevState => {
      return {
        counter: prevState.counter + 1
      };
    });
  };

  render() {
    return (
      <div className="App">
        <p>{this.state.counter}</p>
        <Men adder={this.state.adder} add={this.add} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.body);
Matt Oestreich
  • 8,219
  • 3
  • 16
  • 41