U. Watt
U. Watt

Reputation: 734

Component getting re-rendered when props aren't changing

I'm showing a map that is memoized using React.memo(). I want the map to only re-render when the props change, but it re-renders even when they don't. How can I prevent the re-renders when props don't change?

The Map:

const MapContainer = React.memo((props) => {
  const [map, setMap] = useState();
  var bounds = new props.google.maps.LatLngBounds();

  const setMapObj = (mapProps, map) => {
    setMap(map);
  };

  useDeepCompareEffect(() => {
    if (props.markers.length === 1) {
      bounds.extend(
        new props.google.maps.LatLng(props.markers[0].lat, props.markers[0].lng)
      );
      bounds.extend(
        new props.google.maps.LatLng(
          props.markers[0].lat - 1,
          props.markers[0].lng - 1
        )
      );
      map && map.fitBounds(bounds);
      return;
    }
    for (var i = 0; i < props.markers.length; i++) {
      bounds.extend(
        new props.google.maps.LatLng(props.markers[i].lat, props.markers[i].lng)
      );
    }
    if (map) {
      map.fitBounds(bounds);
    }
  }, [props.markers, map]);

  return (
    <div className={props.className}>
      <Map
        google={props.google}
        style={{ borderRadius: "10px" }}
        // center={{ lat: 59.913, lng: 10.75 }}
        initialCenter={{ lat: 59.913, lng: 10.75 }}
        onReady={setMapObj}
        zoom={8}
      >
        {console.log("rendered")}
        {props.markers.map((item, index) => {
          if (item.lat && item.lng)
            return (
              <Marker key={index} position={{ lat: item.lat, lng: item.lng }} />
            );
        })}
      </Map>
    </div>
  );
});

export default GoogleApiWrapper({
  apiKey: "API_KEY",
})(MapContainer);

Since the parent component is pretty big, I've only added in the parts I think are relevant. The parent of Map:

function Home() {
  const [plants, setPlants] = useState([
    {
      name: "Plant 1",
      id: uuidv4(),
      location: "",
      coords: {},
      country: "",
      isOffshore: false
    },
  ]);

  const isObjectEmpty = (obj) => {
    for (var i in obj) return false;
    return true;
  };

  const getMarkers = () => {
    var temp = [];
    for (var i = 0; i < plants.length; i++)
      if (!isObjectEmpty(plants[i].coords)) temp.push(plants[i].coords);
    return temp;
  };

  return (
    <section/>
    <CustomMap className="map" markers={getMarkers()} />
    <section/>
  )
}

Upvotes: 2

Views: 1079

Answers (1)

Ivan Popov
Ivan Popov

Reputation: 696

  1. Memo does not make a deep comparison.
  2. Each time home is re-render, the map will be re-render too. Because you are passing markers as the result of executing a function that each time generates a new array, a new reference, to a different object. Which of course is perceived by memo as a trigger for a re-render. In addition to the new array, keep in mind that the getMarkers function is also new each time and refers to a different object. For functions, useCallback is usually used.
  3. I propose to use the useMemo hook to fix and exclude the creation of a new array, and, accordingly, a reference to a new object. And accordingly there will be no re-rendering when it is not needed.
  4. Don't forget to include dependencies in useMemo. In your case it is plants.

Example

const markers = useMemo(() => {
  var temp = [];
  for (var i = 0; i < plants.length; i++)
    if (!isObjectEmpty(plants[i].coords)) temp.push(plants[i].coords);
  return temp;
}, [plants]);

Upvotes: 4

Related Questions