4
votes

I have a problem with active link using react-router-bootstrap (https://www.npmjs.com/package/react-router-bootstrap) components. When I navigate to About, active class appears not only on About but in Home too.

Active Link react-router's issue

This my Nav component from react-bootstrap (https://react-bootstrap.github.io/):

NavLinks.jsx

import React, { Component } from 'react';
import { Nav, NavItem } from 'react-bootstrap';

import HashNavItem from '../../Navigation/HashNavItem';

// import Flags from './Flags/Flags'
import "./NavLinks.css";

const routes = [{
  name: 'Home',
  path: '/',
  external: false
},{
  name: 'About',
  path: '/about',
  external: false
}, {
  name: 'Stores',
  path: '/#stores',
  external: false
}, {
  name: 'My Account',
  path: 'http://my-react.appicar.com/',
  external: true
}, {
  name: 'Services',
  path: '/#services',
  external: false
}, {
  name: 'Reviews',
  path: '/#reviews',
  external: false
}, {
  name: 'Contact',
  path: '/#contact',
  external: false
}];

export default class MainNav extends Component {

  handleClick(e) {
    e.preventDefault();
    alert('hola');
  }
  render() {
    let template;

    return (
      <Nav>
        {
          routes.map((route, key) => {
            if (route.external) {
              template = (
                <NavItem eventKey={ 'nav-' + key } href={ route.path } key={ key }>
                  {route.name}
                </NavItem>
              );
            } else {
              template = <HashNavItem eventKey={ 'nav-' + key } name={ route.name } to={ route.path } key={ key } />;
            }
            return template;
          })
        }
      </Nav>
    );
  }
}

HashNavItem.jsx

import React, { Component } from "react";
import { NavItem } from 'react-bootstrap';
import { LinkContainer } from "react-router-bootstrap";

export default class HashNavItem extends Component {

  constructor(props) {
    super(props);
    // Atributes.
    this.hashFragment = '';
    this.observer = null;
    this.asyncTimerId = null;
    this.scrollFunction = null;
    // States.
    this.state = {
      key: props.eventKey,
      name: props.name,
      to: props.to
    }
    // Methods.
    this.reset = this.reset.bind(this);
    this.getElAndScroll = this.getElAndScroll.bind(this);
    this.hashLinkScroll = this.hashLinkScroll.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  reset() {
    this.hashFragment = '';
    if (this.observer !== null) this.observer.disconnect();
    if (this.asyncTimerId !== null) {
      window.clearTimeout(this.asyncTimerId);
      this.asyncTimerId = null;
    }
  }

  getElAndScroll() {
    const element = document.getElementById(this.hashFragment);
    if (element !== null) {
      this.scrollFunction(element);
      this.reset();
      return true;
    }
    return false;
  }

  hashLinkScroll() {
    // Push onto callback queue so it runs after the DOM is updated
    window.setTimeout(() => {
      if (this.getElAndScroll() === false) {
        if (this.observer === null) {
          this.observer = new MutationObserver(this.getElAndScroll);
        }
        this.observer.observe(document, {
          attributes: true,
          childList: true,
          subtree: true,
        });
        // if the element doesn't show up in 10 seconds, stop checking
        this.asyncTimerId = window.setTimeout(() => {
          this.reset();
        }, 10000);
      }
    }, 0);
  }

  handleClick(e) {
    this.reset();
    if (this.props.onClick) this.props.onClick(e);
    if (typeof this.props.to === 'string') {
      this.hashFragment = this.props.to
        .split('#')
        .slice(1)
        .join('#');
    } else if (
      typeof this.props.to === 'object' &&
      typeof this.props.to.hash === 'string'
    ) {
      this.hashFragment = this.props.to.hash.replace('#', '');
    }
    if (this.hashFragment !== '') {
      this.scrollFunction =
        this.props.scroll || (el =>
          el.scrollIntoView(this.props.smooth ? { behavior: 'smooth' } : undefined)
        );
      this.hashLinkScroll();
    }
  }

  render() {
    return (
      <LinkContainer to={ this.state.to }>
        <NavItem eventKey={ this.state.key } key={ this.state.key } onClick={ this.handleClick }>{ this.state.name } </NavItem>
      </LinkContainer>
    );
  }
}

App.jsx

import React, { Component } from "react";
import { Router, Switch, Route } from 'react-router-dom';
import { createBrowserHistory } from 'history';

import MainNav from './MainNav/MainNav';
import Logo from './Logo/Logo';
import Footer from './Footer/Footer';
import Copyright from './Copyright/Copyright';
import HomePage from './HomePage/HomePage';
import AboutPage from './AboutPage/AboutPage';
import Error404 from './Error404/Error404';

import './App.css';

class App extends Component {
  render() {
    return (
      <Router history={ createBrowserHistory() }>
        <div>
          <MainNav />
          <Logo />
          <Switch>
            <Route exact path="/" component={ HomePage } />
            <Route exact path="/about" component={ AboutPage } />
            <Route exact path="/404" component={ Error404 } />
          </Switch>
          <Footer />
          <Copyright />
        </div>
      </Router>
    );
  }
}

export default App;

Does anyone knows why Home is always marked as active?

3

3 Answers

2
votes

You should use IndexLinkContainer instead of LinkContainer

Check here

1
votes

Since you already use react-bootstrap package, then you can fix your issue like this:

import { NavLink } from "react-router-dom";
import { Nav } from "react-bootstrap";
    
<Nav>
    <Nav.Link as={NavLink} exact to="/">
        Home
    </Nav.Link>
    <Nav.Link as={NavLink} exact to="/about">
        About
    </Nav.Link>
</Nav>

The key here is to use the exact property with the Nav.Link component and also to set the as property to NavLink as shown in the above snippet.

Check the documentation of the NavLink's exact property for more info.

0
votes

I believe it has to do with your routes (so react-router not necessarily react-router-bootstrap). I see you put 'exact' in your route declaration but I would try also adding 'strict' to your declaration. In an app I am working on I had to do this in order to get my high-order component validation wrapper to work correctly.

If that doesn't work then my guess would be how you are iterating through the routes array to dynamically create the nav-links. Possibly there is an issue with the package clearing the active state by the way you constructed the nav-links. I am sorry that I don't have a more specific answer! I have yet to get a reputation of 50+ to just be able to leave a comment so a hopefully helpful answer will have to do.