React-Router - Route re-rendering component on route change
Asked Answered
M

3

18

Please read this properly before marking as duplicate, I assure you I've read and tried everything everyone suggests about this issue on stackoverflow and github.

I have a route within my app rendered as below;

<div>
        <Header compact={this.state.compact} impersonateUser={this.impersonateUser} users={users} organisations={this.props.organisations} user={user} logOut={this.logout} />
        <div className="container">
            {user && <Route path="/" component={() => <Routes userRole={user.Role} />} />}
        </div>
    {this.props.alerts.map((alert) =>
            <AlertContainer key={alert.Id} error={alert.Error} messageTitle={alert.Error ? alert.Message : "Alert"} messageBody={alert.Error ? undefined : alert.Message} />)
        }
    </div>

The route rendering Routes renders a component that switches on the user role and lazy loads the correct routes component based on that role, that routes component renders a switch for the main pages. Simplified this looks like the below.

import * as React from 'react';
import LoadingPage from '../../components/sharedPages/loadingPage/LoadingPage';
import * as Loadable from 'react-loadable';

export interface RoutesProps {
    userRole: string;
}

const Routes = ({ userRole }) => {

var RoleRoutesComponent: any = null;
switch (userRole) {
    case "Admin":
        RoleRoutesComponent = Loadable({
            loader: () => import('./systemAdminRoutes/SystemAdminRoutes'),
            loading: () => <LoadingPage />
        });
        break;
    default:
        break;
}

return (
    <div>
        <RoleRoutesComponent/> 
    </div>
);

}

export default Routes;

And then the routes component

const SystemAdminRoutes = () => {

var key = "/";

return (
    <Switch>
        <Route key={key} exact path="/" component={HomePage} />
        <Route key={key} exact path="/home" component={HomePage} />
        <Route key={key} path="/second" component={SecondPage} />
        <Route key={key} path="/third" component={ThirdPage} />
        ...
        <Route key={key} component={NotFoundPage} />
    </Switch>
);
}

export default SystemAdminRoutes;

So the issue is whenever the user navigates from "/" to "/second" etc... app re-renders Routes, meaning the role switch logic is rerun, the user-specific routes are reloaded and re-rendered and state on pages is lost.

Things I've tried;

  • I've tried this with both react-loadable and React.lazy() and it has the same issue.
  • I've tried making the routes components classes
  • Giving all Routes down the tree the same key
  • Rendering all components down to the switch with path "/" but still the same problem.
  • Changing Route's component prop to render.
  • Changing the main app render method to component={Routes} and getting props via redux
  • There must be something wrong with the way I'm rendering the main routes component in the app component but I'm stumped, can anyone shed some light? Also note this has nothing to do with react-router's switch.

    EDIT: I've modified one of my old test project to demonstrate this bug, you can clone the repo from https://github.com/Trackerchum/route-bug-demo - once the repo's cloned just run an npm install in root dir and npm start. I've got it logging to console when the Routes and SystemAdminRoutes are re-rendered/remounted

    EDIT: I've opened an issue about this on GitHub, possible bug

    Route re-rendering component on every path change, despite path of "/"

    Magog answered 9/1, 2019 at 10:37 Comment(5)
    Would it be possible to create a small reproducible codesandbox or even a github repo? It would really help in debugging.Fallonfallout
    @MaazSyedAdeeb I can but it'll take a little while, I'll edit when I get to it if no one's answered firstMagog
    @MaazSyedAdeeb I've uploaded a github repoMagog
    hi have a look at my solution github.com/honzajerabek/route-bug-demo/commit/…Puerility
    Strange, the route switch isn't working at all in that solution now, it's not actually loading any pagesMagog
    M
    10

    Found the reason this is happening straight from a developer (credit Tim Dorr). The route is re-rendering the component every time because it is an anonymous function. This happens twice down the tree, both in App and Routes (within Loadable function), below respectively.

    <Route path="/" component={() => <Routes userRole={user.Role} />} />
    

    needs to be

    <Routes userRole={user.Role} />
    

    and

    loader: () => import('./systemAdminRoutes/SystemAdminRoutes')
    

    Basically my whole approach needs to be rethought

    EDIT: I eventually fixed this by using the render method on route:

    <Route path="/" render={() => <Routes userRole={user.Role} />} />
    
    Magog answered 10/1, 2019 at 16:43 Comment(2)
    You made my day, mate!Hannahhannan
    Using render prop instead of component prop on the Route fixed the problem for me. Do you know the reason?Retinite
    S
    1

    Bumped into this problem and solved it like this:

    In the component:

    import {useParams} from "react-router-dom";
        
    const {userRole: roleFromRoute} = useParams();
    const [userRole, setUserRole] = useState(null);
    
    
    useEffect(()=>{
        setUserRole(roleFromRoute);
    },[roleFromRoute]}
    

    In the routes:

    <Route path="/generic/:userRole" component={myComponent} />
    

    This sets up a generic route with a parameter for the role.

    In the component useParams picks up the changed parameter und the useEffect sets a state to trigger the render and whatever busines logic is needed.

    },[userRole]);

    Shipyard answered 10/2, 2021 at 18:16 Comment(0)
    T
    -1

    Just put the "/" in the end and put the other routes above it. Basically it's matching the first available option, so it matches "/" every time.

    <Switch>
            <Route key={key} exact path="/home" component={HomePage} />
            <Route key={key} path="/second" component={SecondPage} />
            <Route key={key} path="/third" component={ThirdPage} />
            <Route key={key} exact path="/" component={HomePage} />
            <Route key={key} component={NotFoundPage} />
    </Switch>
    
    OR
    
    <Switch>
              <Route path="/second" component={SecondPage} />
              <Route exact path="/" component={HomePage} />
              <Route path="*" component={NotFound} />
    </Switch>
    

    Reorder like this, it will start working. Simple :)

    Tanner answered 30/11, 2021 at 13:18 Comment(0)

    © 2022 - 2024 — McMap. All rights reserved.