1

I am working with a React + Leaflet map and noticing the map container / elements re-render any time I show or close a modal.

I have created a sandbox to demonstrate the problem, but this occurs in other places throughout my app if additional examples would help.

https://codesandbox.io/s/elegant-rain-01f9l?file=/src/App.js

From reading alternative SO posts, such as this very similar one, I recognize this is likely caused by my hooks (ex. handleShow/setShow) which force the entire component to re-render. The unintended behavior is noticed as follows:

If you drag the map so that the current location is out of view and open a modal, we force the re-load of <LeafletControlGeocoder/> and <LocationMarker/>. This is evident because the map re-pans to the current location, and a new 'search icon' appears in the top right on the map.

Steps to replicate:

*If you notice an issue in sandbox related to react-bootstrap/Modal, just update the dependency to latest (refresh icon) - this is a weird sandbox issue but unrelated to my question.

  1. Drag map so that current location is out of view
  2. Click menu icon (top right) > Add Location
  3. When modal appears, notice map re-centers to current location and another search icon appears.
ghybs
  • 47,565
  • 6
  • 74
  • 99
demongman
  • 57
  • 1
  • 9
  • Do not worry about react re-renders. Instead focus on whether the DOM updates and whether that causes performance issues. Try not to pre-optimise code with `memo`, etc. – evolutionxbox Jan 17 '22 at 15:02
  • Hi - thanks for taking a look. This does cause DOM updates, and in addition to performance issues causes unintended elements in the tree. I could change my code to prevent appends / re-pans on the map, but this seems like a bandaid hiding the problem – demongman Jan 17 '22 at 15:06
  • The behavior is correct, every time you call `setState` or in this case `setValue`, it will re-render your DOM, this is a React behavior, but, the problem here is, you should not have components inside components. You should split them in separated files, your current architecutre will cause problems, based on the React lifecycle, since it will re-render every time, it will regenerate the function every time. So, please, try to split your `LeafletControlGeocoder` into a file, and it should work just fine. – Esdras Xavier Jan 17 '22 at 15:13
  • 1
    Try not putting everything in `App`, extract out your Models, maybe call it `` and then have the state save with this.. React works much better if you sub-divide you app into smaller components. – Keith Jan 17 '22 at 15:13
  • @EsdrasXavier "_You should split them in separated files_" actually this is not a requirement, you can very well have several components in the same file. The key however is indeed not to have some components within the body of another... – ghybs Jan 17 '22 at 15:24
  • Thank you all for the suggestions. @Keith From what I'm gathering in your responses, I need to break out my components like and . Does this just mean to define them in a new function outside of App()? For example, I might plan to use this SO as a guide if that's what you mean: https://stackoverflow.com/questions/62047511/in-react-can-i-define-a-functional-component-within-the-body-of-another-functio – demongman Jan 17 '22 at 15:33

1 Answers1

3

The issue is caused by having some child components function defined within the body of another React functional component.

function App() { // A React functional component
  const [show, setShow] = useState(false);
  // etc.

  // A child React functional component in the body of another component...
  function LocationMarker() {
    const [position, setPosition] = useState(null);
    // etc.
  }

  // Another child component
  function LeafletControlGeocoder() {
    useEffect(() => {}, []);
    // etc.
  }

  return (
    <>
      {/* More content... */}
      <MapContainer center={[45.4, -75.7]} zoom={12}>
        <LeafletControlGeocoder />
        <LocationMarker />
      </MapContainer>
    </>
  );
}

This may not be problematic if your child components are stateless (no hook). In that case they are more like "sub-templates".

But if those child components do use some hooks, then being in the body of another functional component will interfere with how the hooks work: because the child component function is re-created at each re-render, React has trouble identifying those children as being the same component, and ends up duplicating their output.

Simply make sure not to define some Functional Components within another one, but always at the top-level of your scope (typically at the root of your file). As mentioned in the question comments, a standard practice is simply to have 1 component per file.

function App() { // A React functional component
  const [show, setShow] = useState(false);
  // etc.

  return (
    <>
      {/* More content... */}
      <MapContainer center={[45.4, -75.7]} zoom={12}>
        <LeafletControlGeocoder />
        <LocationMarker />
      </MapContainer>
    </>
  );
}

// Another React functional component at top scope level
function LocationMarker() {
  const [position, setPosition] = useState(null);
  // etc.
}

// Another component
function LeafletControlGeocoder() {
  useEffect(() => {}, []);
  // etc.
}

Fixed app: https://codesandbox.io/s/lively-monad-yve67

ghybs
  • 47,565
  • 6
  • 74
  • 99
  • 1
    This works - thank you so much ghybs not only for the answer, but for the conceptual background - this is really helpful for me to understand as I continue to learn React. I'd read docs alluding to your point, but had trouble understanding the application until your post. Really appreciate your time! – demongman Jan 17 '22 at 16:44