简体   繁体   中英

React hooks cause unintentional re-render

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM