10

I'm fetching data from an API server to generate the NavBar Menu dynamically.

Problem is that the menu re-renders each time I navigate through the pages. Can't figure out why it's happening. I tried different examples for react-router v4 like using but the menu is always re-rendering.

What pattern do you use to prevent NavBar Menu from re-rendering when generating the Menu dynamically?

Here are the basic setup files:

Main.js file:

import React from 'react'
import { Route } from 'react-router-dom'
import Home2 from './Home'
import Head from './Head'
import Rules from './Rules'

const Main = () => (
  <main>
    <Route  path='/' component={Head}/>
    <Route exact path='/' component={Home}/>
    <Route exact path='/rules' component={Rules}/>
  </main>
)

export default Main

Head.js file:

import React, { Component } from 'react'
import Menu from 'semantic-ui-react'

class Head extends Component {
  constructor(props) {
   super(props);
  }

  getInitialData() {
    //fetch data from server
  }

  componentWillMount() {
    this.getInitialData();
  }

  render() {
    return (
      <header>
        <nav>
          <Menu>
            {/* fetched data */}
        </nav>
      </header>
    )
  }
}

export default Head

Index.js file:

import React from 'react'
import { render } from 'react-dom'
import Main from './components/Main'
import { BrowserRouter } from 'react-router-dom'

render((
  <BrowserRouter>
    <Main />
  </BrowserRouter>
), document.getElementById('root'));

Using React Router v3 this code works fine:

var Routes = (
  <Router>
    <Route path="/" component={Head}>
      <IndexRoute component={Home} />
    </Route>
  </Router>
);

But in v4 I can't nest Routes.

4
  • You want Head to be on every route, and each route have different body component? Home is example of body component? Commented Jan 14, 2018 at 16:16
  • Yes, the Head is basically just the navBar menu an i'd like to have it on all pages. Problem is it re render each time i navigate to an other page and this causes the navBar to 'blink' for a moment (because when it re renders it fetches the data from the server again and this takes some time). And yes Home is the body component. Commented Jan 14, 2018 at 18:16
  • Well you shouldnt have Head in Route. As the answer said extract Head out of Route: <main><Head /> <Route /> <Route /></main> Commented Jan 14, 2018 at 19:24
  • i didn't solve the problem, but that's right! i find out the problem is with semantic-ui-react that i'm using to render the fetched data, and not with react-router. Thanks a lot for the support! :) Commented Jan 14, 2018 at 20:02

4 Answers 4

8

Even though it is late, I thought I'd post the answer here for other people who might be struggling with this.

First of all, as per the answer by @JulesDupont, your <Head /> component should be outside of your routes.

const App = () => (
  <>
    <Head />
    <Switch>
      <Route exact path='/' component={Component 1}/>
      // Add any other routes goes here
    </Switch>
  </>
)

export default App;

Additionally, the pattern that you are searching for is the use of the <Link> tags from react-router-dom. It would be great if you could post your Head component here. There is a strong chance that you are using <a href='/#'> tags to redirect instead of <Link to='/#'> tags inside of your <Menu />.

<a> tags would trigger an entire page reload, causing your <Head /> component to get re mounted each time you navigate to a new page, hence re-fetching all the data. However, the <Link> tag does not not trigger full page reloads.

Example:

import { Link } from 'react-router-dom';

const Head = () => {
    return (
        <ul>
            <Link to='/your-route'>Item 1 (fetches data)<Link>
            <Link to='/your-other-route'>Item 2 (fetches data)</Link>
        </ul>
    )
}

export default Head;

This will ensure that your Head component does not re-render or re-fetches data when navigating to another route.

Sign up to request clarification or add additional context in comments.

1 Comment

I was using 'a' tags and that was my issue. Thank you very much!
5

Because you're including the header as a route, it's re-rendering every time the route changes. Just pull the header out of the route and it will stay consistent while the route-based components change:

import React from 'react'
import { Route, Switch } from 'react-router-dom'
import Home2 from './Home'
import Head from './Head'

const Main = () => (
  <main>
    <Head />
    <Switch>
      <Route exact path='/' component={Home}/>
      // Add any other routes you want here
    </Switch>
  </main>
)

export default Main;

10 Comments

Thanks for the reply. I already tried this solution but it doesn't solve the problem. The menu is re rendering each time i navigate to an other page.
I've used this exact pattern (e.g., on jdupont.github.io) and the header stays constant without re-rendering. How are you defining your other routes?
I edited the post to add a route to Rules Page. It's exactly like Home. I just tried two more times and it's always re-rendering. Could you post an example on [codesandbox.io] or other, to show this pattern does not re-render some wanted component? If so i might have some other problem somewhere.
I downloaded the tutorial's files. I edited the Header component to fetch some data from the server and it doesn't re-render! the structure i'm using is the same, the only thing i'm doing differnetly is that i render the menu using a 'semantic-ui-react' component to show the fetched data. The problem must be with semantic-ui-react and not with react-router as i supposed. Thanks a lot for your support! :)
@JulesDupont what if you need to render a component that is outside of the Main component, like a login page? (Login page without navigation)
|
0

I also had the same issue but it wasn't related to the having the header inside a route. In fact, it was something less obvious that was difficult to pin down.

We were fetching some information from a custom hook, but this custom hook was returning the whole object from the state rather than destructuring the specific fields. Due to this, other fields inside the object (which we weren't interested in) were updating on route change so this was triggering the unnecessary re-render of the header.

Just to provide an example;

What we WERE doing (incorrect)

const { auth } = useSelector(state => state); 

What we changed it to

const { user } = useSelector(state => state.auth);

Other fields inside auth were changing but we weren't interested in those. By destructuring just the user the other fields were ignored and the re-render stopped.

I realise this is probably quite a unique scenario, but hopefully it can help somebody.

Comments

-1

I had a similar problem, in the end I could not solve it, so try the following. in the update of my main route, request menu, if I do not have it created.

    #Route.js

    state = {
        menu: null
    };

    componentDidUpdate() {
            if ( !this.state.menu ) {
                this._instanceMenu();
            }
    } 

    render() {
            return (
                    <React.Fragment>
                            { this._renderStaticContent() }
                            <Switch>
                                    <Route exact path={ '/' } component={ DefaultPage } />
                                    <Route component={ Error404 } />
                            </Switch>
                    </React.Fragment>
            );
    };

The first time I request it by fetch, then I store it in localstorage, later, if it is already in localStorage, I do not return to make a fetch.

    #Route.js

    _instanceMenu() {
            if ( !this.helper.getMenu() ) {
                    this.helper.instanceMenu( ( menu ) => {
                            this.setState( { menu } );
                    } );
            }

            this.setState( { menu: this.helper.getMenu() } );
    }

Optional

    #Helper.js

    instanceMenu = ( callback ) => {
            axios.get(
                    this._urlMenu,
            ).then( ( response ) => {
                    localStorage.setItem( this._menu, JSON.stringify( response.data ) );
                    callback( this.getMenu() );
            } ).catch( ( error ) => {
                    console.log( error );
            } );
    };

    getMenu = () => {
            return JSON.parse( localStorage.getItem( this._menu ) );
    };

finally I send it to my component with a Higer-Order Component (Readme)

     #Route.js

     _renderStaticContent = () => {
            if ( !this.authService.isLoggedIn() ) return null;

            return <React.Fragment>
                    <Route component={ HeaderContainer } />
                    <Route component={ wrap( SidebarContainer, { 'menu': this.state.menu } ) } />
                    <Route component={ Footer } />
            </React.Fragment>;
    };

PD: Sorry for my english.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.