Sharcoux
Sharcoux

Reputation: 6075

React rerendering children in functional component despite using memo and not having any prop changed

I have an Icon component that draw an icon and which is blinking because the parent is making it rerender for nothing. I don't understand why this is happening and how to prevent this.

Here is a snack that shows the issue.

We emulate the parent changes with a setInterval.

We emulate the icon rerendering by logging 'rerender' in the console.

Here is the code:

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';


// or any pure javascript modules available in npm
let interval = null

const Child = ({name}) => {
  //Why would this child still rerender, and how to prevent it?
  console.log('rerender')
  return <Text>{name}</Text>
}
const ChildContainer = ({name}) => {
  const Memo = React.memo(Child, () => true)
  return <Memo name={name}/>
}

export default function App() {
  const [state, setState] = React.useState(0)
  const name = 'constant'
  // Change the state every second
  React.useEffect(() => {
    interval = setInterval(() => setState(s => s+1), 1000)
    return () => clearInterval(interval)
  }, [])
  return (
    <View>
      <ChildContainer name={name} />
    </View>
  );
}

If you could explain me why this is happening and what is the proper way to fix it, that would be awesome!

Upvotes: 2

Views: 2483

Answers (1)

Michalis Garganourakis
Michalis Garganourakis

Reputation: 2930

If you move const Memo = React.memo(Child, () => true) outside the ChildContainer your code will work as expected.

While ChildContainer is not a memoized component, it will be re-rendered and create a memoized Child component on every parent re-render.

By moving the memoization outside of the ChildContainer, you safely memoize your component Child once, and no matter how many times ChildContainer will be called, Child will only run one time.

Here is a working demo. I also added a log on the App to track every re-render, and one log to the ChildComponent so you can see that this function is called on every re-render without actually touching Child anymore.

You could also wrap Child with React.memo directly:

import * as React from "react";
import { Text, View, StyleSheet } from "react-native";

// or any pure javascript modules available in npm
let interval = null;

const Child = React.memo(({ name }) => {
  //Why would this child still rerender, and how to prevent it?
  console.log("memoized component rerender");
  return <Text>{name}</Text>;
}, () => true);

const ChildContainer = ({ name }) => {
  console.log("ChildContainer component rerender");
  return <Child name={name} />;
};

export default function App() {
  const [state, setState] = React.useState(0);
  const name = "constant";
  // Change the state every second
  React.useEffect(() => {
    interval = setInterval(() => setState(s => s + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  console.log("App rerender");
  return (
    <View>
      <ChildContainer name={name} />
    </View>
  );
}

Upvotes: 6

Related Questions