Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-Router - Route re-rendering component on route change

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 "/"

    like image 863
    James Morrison Avatar asked Jan 09 '19 10:01

    James Morrison


    People also ask

    Does React router re-render?

    withRouter does not subscribe to location changes like React Redux's connect does for state changes. Instead, re-renders after location changes propagate out from the <Router> component. This means that withRouter does not re-render on route transitions unless its parent component re-renders.

    Why is my component Rerendering React?

    React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.

    Does Redux re-render component on state change?

    New! Save questions or answers and organize your favorite content. Learn more.


    1 Answers

    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} />} />
    
    like image 118
    James Morrison Avatar answered Oct 13 '22 00:10

    James Morrison