admin管理员组

文章数量:1355014

I’m working on a React application using react-router-dom (version 6.26.1) and encountering an unexpected re-rendering issue. I have a Counter component that updates its state every second to display a timer, and a navigation bar with Link components. The Counter and the nav (containing the Link components) are siblings under BrowserRouter. When the Counter updates its state, the Link components re-render, even though there’s no direct dependency between them (e.g., no shared state, props, or routing hooks in Counter). I’m trying to understand why this is happening.

Code Setup Here’s a simplified version of my application:

// App.js
import { useEffect } from "react";
import { Link, BrowserRouter } from "react-router-dom";
import Counter from "./components/Counter";

const App = () => {
  useEffect(() => {
    console.log("App rendered");
  }, []);

  return (
    <div className="App">
      <BrowserRouter>
        <nav>
          <ul>
            <li>
              <Link to="/route_one">Route One</Link>
            </li>
            <li>
              <Link to="/route_one">Route One</Link>
            </li>
          </ul>
        </nav>
        <Counter />
      </BrowserRouter>
    </div>
  );
};

export default App;

// ======

// Counter.js
import React, { useEffect, useState } from "react";

const Timer = (props) => {
  const { isPaused } = props;
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    if (isPaused) {
      return;
    }

    const interval = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);
    console.log("Effect has run");
    return () => {
      clearInterval(interval);
      console.log("Timer cleaned up");
    };
  }, [isPaused]);

  return <p>Seconds: {seconds}</p>;
};

const Counter = function () {
  const [showTimer, setShowTimer] = useState(true);
  const [isPaused, setIsPaused] = useState(false);

  const handleTogglePlay = () => {
    setIsPaused((prev) => !prev);
  };

  return (
    <div>
      {showTimer && <Timer isPaused={isPaused} />}
      <button onClick={() => setShowTimer(!showTimer)}>
        {showTimer ? "Hide Timer" : "Show Timer"}
      </button>
      <button onClick={handleTogglePlay}>
        {isPaused ? "Play" : "Pause"}
      </button>
    </div>
  );
};

export default Counter;

What I Expect

The Timer component updates its seconds state every second, causing itself to re-render. Since Timer’s state is local and doesn’t affect Counter’s props or state, Counter shouldn’t re-render unless showTimer or isPaused changes. The nav and its Link components, which are siblings of Counter under BrowserRouter, should not re-render because:

  • There’s no shared state or props between Counter and nav.
  • Timer doesn’t use any react-router-dom hooks (e.g., useLocation), so it shouldn’t interact with the routing context.
  • The location isn’t changing (I’m staying on the same route).

What I Observe

Using React DevTools, I see that the Link components re-render every second, matching the timer’s update frequency.

The React Profiler shows that Link re-renders because its parent (Location_Provider, an internal component of react-router-dom) re-renders. Here’s a screenshot of the profiler:

Additionally, when I enable "Highlight updates when components render" in React DevTools, I see highlights on the Link components and their parents every second as the timer updates.

My Confusion

I don’t understand why Location_Provider (or another parent component in react-router) re-renders when Timer updates its state. There’s no direct dependency between Timer and the routing context, and the location isn’t changing. I expected that a child component’s state change (Timer) would only cause itself to re-render, not its siblings (Link) or parents (Location_Provider).

What I’ve Tried

I confirmed that App only renders once (via the console.log in useEffect), so the children prop passed to BrowserRouter is stable. I checked that Timer doesn’t use any routing hooks, so it shouldn’t be subscribing to the routing context.

Questions

Why does Location_Provider re-render when Timer updates its state, even though there’s no location change or direct dependency?

Is this a known behavior in react-router(v7.4.1), where internal components like Location_Provider re-render during reconciliation, even without context changes?

How can I prevent Link from re-rendering in this scenario, or is this expected behavior that I should accept?

I’m not concerned about performance at this stage (the re-renders are fast), but I want to understand why this is happening to improve my understanding of React and react-router-dom. Any insights would be appreciated!

本文标签: reactjsWhy does Link rerender when a sibling component updates its state in React RouterStack Overflow