最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React Router V6 Re-renders on Route Change - Stack Overflow

programmeradmin1浏览0评论

When I navigate back and forth between routes, React Router re-renders memoized routes causing useEffect(() => []) to re-run and data to re-fetch. I'd like to prevent that and instead keep existing routes around but hidden in the dom. I'm struggling with "how" though.

The following is sample code for the problem:

import React, { useEffect } from "react";
import { BrowserRouter as Router, Route, Routes, useNavigate } from "react-router-dom";

export default function App() {
  return (
    <Router>
      <Routes>
        <Route path={"/"} element={<MemoizedRouteA />} />
        <Route path={"/b"} element={<MemoizedRouteB />} />
      </Routes>
    </Router>
  );
}

function RouteA() {
  const navigate = useNavigate()
  useEffect(() => {
    alert("Render Router A");
  }, []);

  return (
      <button onClick={() => { navigate('/b') }}>Go to B</button>
  );
};

const MemoizedRouteA = React.memo(RouteA)

function RouteB() {
  const navigate = useNavigate()

  useEffect(() => {
    alert("Render Router B");
  }, []);

  return (
    <button onClick={() => { navigate('/') }}>Go to A</button>
  );
}

const MemoizedRouteB = React.memo(RouteB)

Sandbox: =/src/App.js

With the above code, you'll see that the "alert" code is called whenever you tap a button or use the browser back button.

With there being so many changes of React Router over the years I'm struggling to find a solution for this.

When I navigate back and forth between routes, React Router re-renders memoized routes causing useEffect(() => []) to re-run and data to re-fetch. I'd like to prevent that and instead keep existing routes around but hidden in the dom. I'm struggling with "how" though.

The following is sample code for the problem:

import React, { useEffect } from "react";
import { BrowserRouter as Router, Route, Routes, useNavigate } from "react-router-dom";

export default function App() {
  return (
    <Router>
      <Routes>
        <Route path={"/"} element={<MemoizedRouteA />} />
        <Route path={"/b"} element={<MemoizedRouteB />} />
      </Routes>
    </Router>
  );
}

function RouteA() {
  const navigate = useNavigate()
  useEffect(() => {
    alert("Render Router A");
  }, []);

  return (
      <button onClick={() => { navigate('/b') }}>Go to B</button>
  );
};

const MemoizedRouteA = React.memo(RouteA)

function RouteB() {
  const navigate = useNavigate()

  useEffect(() => {
    alert("Render Router B");
  }, []);

  return (
    <button onClick={() => { navigate('/') }}>Go to A</button>
  );
}

const MemoizedRouteB = React.memo(RouteB)

Sandbox: https://codesandbox.io/s/wonderful-hertz-w9qoip?file=/src/App.js

With the above code, you'll see that the "alert" code is called whenever you tap a button or use the browser back button.

With there being so many changes of React Router over the years I'm struggling to find a solution for this.

Share Improve this question edited Sep 7, 2022 at 4:36 Drew Reese 204k18 gold badges245 silver badges273 bronze badges asked Sep 7, 2022 at 1:16 Luke RhodesLuke Rhodes 3233 silver badges13 bronze badges 1
  • learn to use react-query package, it'll handle all the memoization for you. We can handle and scale the big application using it. – bhargava.prabu Commented Sep 7, 2022 at 4:38
Add a ment  | 

1 Answer 1

Reset to default 4

When I navigate back and forth between routes, React Router re-renders memoized routes causing useEffect(() => []) to re-run and data to re-fetch. I'd like to prevent that and instead keep existing routes around but hidden in the dom. I'm struggling with "how" though.

Long story short, you can't. React ponents rerender for one of three reasons:

  1. Local ponent state is updated.
  2. Passed prop values are updated.
  3. The parent/ancestor ponent updates.

The reason using the memo HOC doesn't work here though is because the Routes ponent only matches and renders a single Route ponent's element prop at-a-time. Navigating from "/" to "/b" necessarily unmounts MemoizedRouteA and mounts MemoizedRouteB, and vice versa when navigating in reverse. This is exactly how RRD is intended to work. This is how the React ponent lifecycle is intended to work. Memoizing a ponent output can't do anything for when a ponent is being mounted/unmounted.

If what you are really trying to minimize/reduce/avoid is duplicate asynchronous calls and data fetching/refetching upon ponent mounting then what I'd suggest here is to apply the Lifting State Up pattern and move the state and useEffect call into a parent/ancestor.

Here's a trivial example using an Outlet ponent and its provided context, but the state could be provided by any other means such as a regular React context or Redux.

import React, { useEffect, useState } from "react";
import {
  BrowserRouter as Router,
  Route,
  Routes,
  Outlet,
  useNavigate,
  useOutletContext
} from "react-router-dom";

export default function App() {
  const [users, setUsers] = useState(0);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode./users")
      .then((response) => response.json())
      .then(setUsers);
  }, []);

  return (
    <Router>
      <Routes>
        <Route element={<Outlet context={{ users }} />}>
          <Route path={"/"} element={<RouteA />} />
          <Route path={"/b"} element={<RouteB />} />
        </Route>
      </Routes>
    </Router>
  );
}

function RouteA() {
  const navigate = useNavigate();

  return (
    <div>
      <button onClick={() => navigate("/b")}>Go to B</button>
    </div>
  );
}

function RouteB() {
  const navigate = useNavigate();
  const { users } = useOutletContext();

  return (
    <div>
      <button onClick={() => navigate("/")}>Go to A</button>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} : {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

发布评论

评论列表(0)

  1. 暂无评论