Reputation: 103
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.
Upvotes: 10
Views: 15563
Reputation: 81
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.
Upvotes: 8
Reputation: 466
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.
Upvotes: 0
Reputation: 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.
Upvotes: -1
Reputation: 7577
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;
Upvotes: 5