Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Wrapper for component passed in the "element" prop for the <Route /> does not re-render when switching routes #8551

Closed
kuubson opened this issue Jan 3, 2022 · 3 comments
Labels

Comments

@kuubson
Copy link

kuubson commented Jan 3, 2022

What version of React Router are you using?

6.2.1

Steps to Reproduce

I've tested this code with clear cra typescript template to make sure none of dependencies or app logic affects this behavior but a bug described below still occurs there.

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

const Profile = () => {
  useEffect(() => {
    console.log("profile");
  }, []);
  return <div>profile</div>;
};

const Chat = () => {
  useEffect(() => {
    console.log("chat");
  }, []);
  return <div>chat</div>;
};

const Navbar = () => {
  const navigate = useNavigate();
  return (
    <nav>
      <h1 onClick={() => navigate("/")}>Profile</h1>
      <h1 onClick={() => navigate("/chat")}>Chat</h1>
    </nav>
  );
};

const ProtectedRoute: React.FC = ({ children }) => {
  useEffect(() => {
    console.log(
      'this should log every time we switch between "/" and "/profile" route'
    );
  }, []);
  return (
    <div>
      <Navbar />
      {children}
    </div>
  );
};

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route
          path="/"
          element={
            <ProtectedRoute>
              <Profile />
            </ProtectedRoute>
          }
        />
        <Route
          path="/chat"
          element={
            <ProtectedRoute>
              <Chat />
            </ProtectedRoute>
          }
        />
      </Routes>
    </BrowserRouter>
  );
};

export default App;

Expected Behavior

Component ProtectedRoute should fire its console.log from useEffect every time I switch between / and /profile routes.

It doesn't work because the ProtectedRoute isn't re-rendered.

Only those nested components within ProtectedRoute are re-rendered. (Profile and Chat )

Actual Behavior

When I pass element to the Route like that:

<Route
   path="/"
   element={
     <ProtectedRoute>
       <Profile />
     </ProtectedRoute>
   }
/>;

it only re-renders Profile component when switching routes: console.log from Profile component fires but console.log from ProtectedRoute does not.

When I pass it this way:

const ProfileRoute = () => (
  <ProtectedRoute>
    <Profile />
  </ProtectedRoute>
);

<Route path="/" element={<ProfileRoute />} />

It start re-rendering both ProtectedRoute and Profile when switching between routes so both console.logs start working

@kuubson kuubson added the bug label Jan 3, 2022
@timdorr
Copy link
Member

timdorr commented Jan 3, 2022

This is just how React works. You're re-rendering the same component tree with no props changes or hooks that would trigger a render.

You can fix this by subscribing to the location by adding a useLocation hook in your component and make it a dependency of your useEffect.

@timdorr timdorr closed this as completed Jan 3, 2022
@kuubson
Copy link
Author

kuubson commented Jan 4, 2022

It's not even about having useLocation as a dependency in useEffect inside ProtectedRoute to make ProtectedRoute log each time we switch the route of the app (I mistook it above - I mean toggling between "/" and "/chat" paths).

I don't want to force it to re-render.

Shouldn't the default behavior of Routes component be to just render anew the whole element of Route each time the route of the app matches the Route's path??

Consider these two scenarios:

  1. When I pass element prop as a JSX.Element (the first code example from Actual Behavior above):

    • it renders ProtectedRoute only once and fires its console.log from useEffect only once while running the entire application. During toggling routes, it stays rendered as if it had only inital render because its console.log does not fire anymore. Calling API doesn't work too. Only its children seems to fire their console.log from useEffect on every route change.
  2. When I pass element prop as a function that returns JSX.Element (the second code example from Actual Behavior above):

    • it renders anew ProtectedRoute and its children every time I toggle routes. console.log inside useEffect of ProtectedRoute starts firing on every route change

Prove me wrong but I still see scenario no. 1 as a form of a bug. It should at least yall at me that passing nested JSX.Element isn't allowed because it can't handle it properly

@zhaohongyan
Copy link

I seem to have the same problem when I'm using the Baidu app, but the local development environment is fine.

brophdawg11 pushed a commit that referenced this issue Mar 27, 2024
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants