简体   繁体   中英

React-Router v4 shared layout re-renders

I know that this question has been asked before, but I keep having issues with this.

The issue I have is that when I use a Page Layout -like component to wrap my routes, this page layout is re-rendered when changing path.

In react-router v3 I did something like this:

<Router history={this.props.history}>
  <Route path="/">
    <IndexRedirect to="/Dossiers" />
    <Route path="/Dossiers" component={MainLayout}>
      <IndexRoute component={DossiersPage} />
      <Route path="/Dossiers/:dossierId/:title" component={DossierDetailsPage} />
    </Route>
  </Route>
</Router>

When moving paths, this would NOT re-render the MainLayout component (which is easily checked by putting something in state inside MainLayout ).

Now, in react-router v4 I tried a couple of approaches already:

However, all solutions I've tried seem to re-render the MainLayout component, basically causing state to reset to its initial value(s).


tldr; How do I create a wrapping component in react-router v4 which doesn't re-render when changing paths

I put together a codesandbox example of how I'm using a "page layout" type of component. It uses React Router v4.1.2.

https://codesandbox.io/s/Vmpy1RzE1

As you described in your question, and as was described in Matt's answer, the MainLayout component wraps the routes.

<BrowserRouter>
  <MainLayout>
    <Switch>
      <Route path="/" exact component={Home} />
      <Route path="/about" exact component={About} />
    </Switch>
  </MainLayout>
</BrowserRouter>

It is true that the MainLayout component re-renders when I navigate the app, in the sense that render is called by React. But, the MainLayout component is never unmounted, so the state never re-initializes.

I've placed some console.log s around my example to show this. My MainLayout looks like this:

export default class MainLayout extends React.Component {
  state = {
    layoutCreatedOn: Date(),
  };

  componentDidMount() {
    //This will not fire when we navigate the app.
    console.log('layout did mount.');
  }

  componentWillUnmount() {
    //This won't fire,
    // because our "shared page layout" doesn't unmount.
    console.log('layout will unmount');
  }

  render() {
    //This does fire every time we navigate the app.
    // But it does not re-initialize the state.
    console.log('layout was rendered');

    return (
      <div styles={styles}>
        <h5>
          Layout created: {this.state.layoutCreatedOn}
        </h5>
        <Sidebar />
        {this.props.children}
      </div>
    );
  }
}

As you click around the app, you'll see a few things.

  1. componentDidMount fires only once.
  2. componentWillUnmount never fires.
  3. render fires every time you navigate.
  4. Despite this, my layoutCreatedOn property shows the same time as I navigate the app. The state is initialized when the page loads, and never re-initialized.

You no longer need IndexRedirect , instead just wrap all of your routes in your MainLayout component, such as:

<Router history={this.props.history}>
  <Switch>
    <MainLayout>
      <Route path="/" component={DossiersPage}/>
      <Route path="/Dossiers/:dossierId/:title" component={DossierDetailsPage} />
    </MainLayout>
  </Switch>
</Router>

Here is the correct solution for React Router v4 as stated here

So basically you need to use the render method to render the layout and wrap your component like this:

<Router>
  <Switch>
    <Route path={ROUTES.LOGIN} render={props => 
      <LoginLayout {...props}>
        <Login {...props} />
      </LoginLayout>
    } />
    <Route path={ROUTES.REGISTER} render={props => 
      <LoginLayout {...props}>
        <Register {...props} />
      </LoginLayout>
    } />

    <Route path="*" component={NotFound} />
  </Switch>
</Router>

This will not cause re-rendering of the layout when you are changing the routes.

When you have many different components with many different layouts you can go ahead and define them in a route config array like the example from the issue I linked :

const routes = [
  { path: '/',
    exact: true,
    component: Home
  },
  { path: '/about',
    component: About,
  },
  { path: '/cart',
    component: Three,
  }
]

<Router>
  <Switch>
    {routes.map({ path, exact, component: Comp } => (
      <Route path={path} exact={exact} render={(props) => (
        <LayoutWithSidebarAndHeader {...props}>
          <Comp {...props}/>
        </LayoutWithSidebarAndHeader>
      )}/>
    ))}
    <Route component={Error404}/>
  </Switch>
</Router>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM