How do I prevent my Google Map from resetting when parent component's state is updated?

36 views Asked by At

I need to prevent my Google Map from reverting to its original center and zoom settings every time its parent component re-renders.

I have a parent component that contains two main child components: a Google Map and a sidebar. On the screen it looks like this:

enter image description here

My intention is to populate the sidebar with information when a particular marker is clicked. Here's the code:

PARENT:

import React, { useState } from "react";
import { styled } from "@mui/material/styles";
import MapComponent from "../Components/Map2";

export default function ACA() {
  const [info, setInfo] = useState(null);

  const FlexContainer = styled("div")(({ theme }) => ({
    width: "98vw",
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: 4,
    [theme.breakpoints.down("md")]: {
      flexDirection: "column",
    },
  }));

  const updateSidebar = (elec) => {
    setInfo(elec);
  };

  return (
    <div
      style={{
        minHeight: "100vh",
        minWidth: "100vw",
        background: "linear-gradient(to bottom, black, midnightblue)",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        paddingTop: 10,
      }}
    >
      <FlexContainer>
        <MapComponent updateSidebar={updateSidebar} />
        <div
          style={{
            height: "650px",
            maxHeight: "80vh",
            width: "650px",
            maxWidth: "98vw",
            border: "2px solid black",
            backgroundColor: "whitesmoke",
            borderRadius: 8,
          }}
        >
          <h4>{info}</h4>
        </div>
      </FlexContainer>
    </div>
  );
}

MAPCOMPONENT

import React, { useEffect, useState } from "react";
import { APIProvider, Map, AdvancedMarker } from "@vis.gl/react-google-maps";
import Axios from "axios";

export default function MapComponent({ updateSidebar }) {
  const [markers, setMarkers] = useState([]);

  useEffect(() => {
    const getElectorates = async () => {
      try {
        const elecs = await Axios.get("http://localhost:3930/admin/getMapData");
        setMarkers(elecs.data);
      } catch (err) {
        console.log(err);
      }
    };

    getElectorates();
  }, []);

  return (
    <APIProvider apiKey="<redacted>">
      <Map
        style={{
          width: "650px",
          height: "650px",
          maxWidth: "80vw",
          maxHeight: "80vh",
        }}
        defaultCenter={{ lat: -23.698, lng: 133.8807 }}
        defaultZoom={4}
        gestureHandling={"greedy"}
        disableDefaultUI={true}
        mapId="<redacted>"
      >
        {markers.map((el, i) => {
          return (
            <AdvancedMarker
              position={el.coords}
              key={i}
              onClick={() => {
                updateSidebar(el.electorate);
              }}
            />
          );
        })}
      </Map>
    </APIProvider>
  );
}

The issue is that whenever I click on a marker and execute the updateSidebar function, the parent component's state is updated which in turn causes the map to revert to its original center and zoom settings. How can I prevent this from happening?

1

There are 1 answers

0
Yrll On

You should define your FlexContainer component outside your parent component

There's nothing wrong with your implementation of the MapComponent. But from what I can see from your code, you have defined FlexContainer within your main component ACA like so:

export default function ACA() {
  const [info, setInfo] = useState(null);

  const FlexContainer = styled("div")(({ theme }) => ({
    width: "98vw",
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: 4,
    [theme.breakpoints.down("md")]: {
      flexDirection: "column",
    },
  }));
  //....The rest of the code

Wherein you also returned it within the ACA component:

return (
  <div
    style={{
      minHeight: "100vh",
      minWidth: "100vw",
      background: "linear-gradient(to bottom, black, midnightblue)",
      display: "flex",
      flexDirection: "column",
      alignItems: "center",
      paddingTop: 10,
    }}
  >
    <FlexContainer>
      <MapComponent updateSidebar={updateSidebar} />
      <div
        style={{
          height: "650px",
          maxHeight: "80vh",
          width: "650px",
          maxWidth: "98vw",
          border: "2px solid black",
          backgroundColor: "whitesmoke",
          borderRadius: 8,
        }}
      >
        <h4>{info}</h4>
      </div>
    </FlexContainer>
  </div>
);

This causes your FlexContainer to be redefined everytime the main component gets rerendered. Because of it, everything else inside it gets redefined and rebuilt also. That's why your MapComponent resets to its original state.

To fix this, you must define FlexContainer outside of your main/parent component ACA. In this way, your FlexContainer is already defined regardless of how many times your parent component rerenders. Then this would not also cause your MapComponent to be rebuilt from scratch.

With that said, your parent component's code should look something like this:

import React, { useState } from "react";
import { styled } from "@mui/material/styles";
import MapComponent from "../Components/Map2";

export default function ACA() {
  const [info, setInfo] = useState(null);

  // Removed the FlexContainer definition here, and moved it outside
  // this component.

  const updateSidebar = (elec) => {
    setInfo(elec);
  };

  return (
    <div
      style={{
        minHeight: "100vh",
        minWidth: "100vw",
        background: "linear-gradient(to bottom, black, midnightblue)",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        paddingTop: 10,
      }}
    >
      <FlexContainer>
        <MapComponent updateSidebar={updateSidebar} />
        <div
          style={{
            height: "650px",
            maxHeight: "80vh",
            width: "650px",
            maxWidth: "98vw",
            border: "2px solid black",
            backgroundColor: "whitesmoke",
            borderRadius: 8,
          }}
        >
          <h4>{info}</h4>
        </div>
      </FlexContainer>
    </div>
  );
}

// Move the FlexContainer component definition here
const FlexContainer = styled("div")(({ theme }) => ({
    width: "98vw",
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: 4,
    [theme.breakpoints.down("md")]: {
      flexDirection: "column",
    },
  }));

Then this should fix your issue.

Here's a proof of concept codesandbox that you can try and test out yourself.

NOTE: I made a sample ContainerDiv in replacement to your FlexContainer. But it still proves the point of this answer.