React-router: react-bootstrap compatibility

Created on 17 Jul 2014  路  18Comments  路  Source: ReactTraining/react-router

I'd like to combine react-bootstrap with react-nested-router.
e.g. defining a navbar looks like this:

var navbarInstance = (
    <Navbar>
      <Nav>
        <NavItem key={1} href="#">Link</NavItem>
        <NavItem key={2} href="#">Link</NavItem>
        <DropdownButton key={3} title="Dropdown">
          <MenuItem key="1">Action</MenuItem>
          <MenuItem key="2">Another action</MenuItem>
          <MenuItem key="3">Something else here</MenuItem>
          <MenuItem divider />
          <MenuItem key="4">Separated link</MenuItem>
        </DropdownButton>
      </Nav>
    </Navbar>
  );

The problem here is that NavItem renders to an <li> element with an <a> tag. Do you have an Idea how I can tell the NavItem that it should use the Link class of react-nested-router?

Most helpful comment

From NavItem.js and SafeAnchor.js:

    import { Link } from 'react-router';
    // ...
    <Nav>
        <NavItem componentClass={Link} href="/economies" to="/economies">Economies</NavItem>
        <NavItem componentClass={Link} href="/industries" to="/industries">Industries</NavItem>
    </Nav>

All 18 comments

Hey @flosse, its not currently not possible to override the <a> component within a NavItem in react-bootstrap, but you can provide your own custom Link like behaviour, for example:

var navbarInstance = (
    <Navbar>
      <Nav>
        <NavItem key={1} href="#" onClick={Router.transitionTo.bind(null, 'link1')}>Link</NavItem>
        <NavItem key={2} href="#" onClick={Router.transitionTo.bind(null, 'link2')}>Link</NavItem>
        <DropdownButton key={3} title="Dropdown">
          <MenuItem key="1" onClick={Router.transitionTo.bind(null, 'action1')}>Action</MenuItem>
        </DropdownButton>
      </Nav>
    </Navbar>
  );

hmm...and how can I access the current active link? I need to set the active property.
Is there something like Router.getCurrent()?

related #85

ok, here is my current hacky workaround:

module.exports = React.createClass

  isActive: (name) -> window.location.hash.indexOf('#/' + name) isnt -1

  onSelect: (id) -> Router.transitionTo.bind null, id

  render: ->
    Navbar null,
      Nav null,
        for l, key in links
          props = { key, onSelect: @onSelect(l.id), active: @isActive(l.id) }
          NavItem props, l.title

Like mentioned in #85 it would be nice to extend the API to avoid such hacks.

I think react-bootstrap should support specifying a custom link class. Would you mind opening an issue there?

As a horrible hack, would something like this work temporarily?

var originalAnchor = React.DOM.a;
Router.Link.componentConstructor.prototype.render = function () {
    var props = {
            href: this.getHref(),
            className: this.getClassName(),
            onClick: this.handleClick
        };

        return originalAnchor(props, this.props.children);
};
React.DOM.a = Router.Link;

Shudder.

uuhh.... React.DOM.a = Router.Link; looks dangerous. What if you'd like to render a plain a element?

yeah, it's totally deranged - I'm certainly not recommending it.

But <originalAnchor></originalAnchor> would work

85624828766f3794ce42523dff645542091f95d1 adds an ActiveState mixin that lets you mixin "active" behavior to any React component.

For the next person who googles this, I believe the current way to do this in react-router is to use

browserHistory.push(url)

Thus the code could look like

  goToUrl: url => event => {
    event.preventDefault();
    browserHistory.push(url)
  },

<NavItem eventKey={1} href="#" onClick={this.goToUrl('/profile')}>Profile</NavItem>

From NavItem.js and SafeAnchor.js:

    import { Link } from 'react-router';
    // ...
    <Nav>
        <NavItem componentClass={Link} href="/economies" to="/economies">Economies</NavItem>
        <NavItem componentClass={Link} href="/industries" to="/industries">Industries</NavItem>
    </Nav>

Making this big so it's easier to see. The official recommendation of the maintainers of React Router and React-Bootstrap is:

Use React-Router-Bootstrap

That componentClass={Link} trick is clever, but it won't give you the right active state, the way RRB's <LinkContainer> will.

Isn't it easier to just do

    import React, { PropTypes } from 'react';
    import { Link, IndexLink } from 'react-router';
    import SafeAnchor from 'react-bootstrap/lib/SafeAnchor';

    function NavLink(props, context) {
        const { tagName, children, componentClass, href, to, active, index } = props;
        const newProps = {
            componentClass: componentClass || (index ? IndexLink : Link),
            to,
            href: href || to,
            active: active || context.router.isActive(to),
        };
        const Component = tagName || SafeAnchor;
        return (
            <Component {...newProps}>{children}</Component>
        );
    }

    NavLink.contextTypes = {
        router: PropTypes.object,
    };

    NavLink.propTypes = {
        tagName: PropTypes.any,
        children: PropTypes.node,
        componentClass: PropTypes.any,
        href: PropTypes.any,
        to: PropTypes.any,
        active: PropTypes.bool,
        index: PropTypes.bool,
    };

    export default NavLink;

Cloning elements is the standard API here, and is just about free outside of dev mode. RRB previously had a per-element wrappers, but the API just got unwieldy. A single container "just works".

Wrap it like this, I made huge mistake here:

          <Nav>
            <LinkContainer to="/link1">
              <NavItem>Item 1</NavItem>
            </LinkContainer>
            <LinkContainer to="/link2">
              <NavItem>Item 2</NavItem>
            </LinkContainer>
          </Nav>

I did it the same way as @fengerzh and I'm getting an ' React.createElement: type should not be null, undefined, boolean, or number. ' error. What am I doing wrong?

import { Navbar, Nav, NavItem } from 'react-bootstrap';
import LinkContainer from 'react-router-bootstrap';

<Nav>
  <LinkContainer to='/view'>
    <NavItem>test</NavItem>
  </LinkContainer>
  <NavItem eventKey={2} href="#">Link</NavItem>
</Nav>

Sorry for late reply, I did not check my email frequently.

Actually, the whole code is as below:

import React, { Component, PropTypes } from 'react';
import { Navbar, Nav, NavItem } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loggedIn: 1,
    };
  }

  render() {
    return (
      <div>
        <Navbar>
          <Navbar.Header>
            <Navbar.Brand>
              <a href="/">My space</a>
            </Navbar.Brand>
            <Navbar.Toggle />
          </Navbar.Header>
          <Navbar.Collapse>
            <Nav>
              <LinkContainer to="/url-to-link1">
                <NavItem>Item 1</NavItem>
              </LinkContainer>
              <LinkContainer to="/url-to-link2">
                <NavItem>Item 2</NavItem>
              </LinkContainer>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <div className="content">
          {this.props.children}
        </div>
      </div>
    );
  }
}

Main.propTypes = {
  children: PropTypes.element.isRequired,
};

export default Main;

As recommended in upvoted comments, I tried to use the react-router-bootstrap.
When following exactly the exact examples shared, I do get this error:

router.createHref is not a function
TypeError: router.createHref is not a function
    at LinkContainer.render (/node_modules/react-router-bootstrap/lib/LinkContainer.js:126:27)
    at processChild (/node_modules/react-dom/cjs/react-dom-server.node.development.js:2207:18)

It's crashing at this line of LinkContainer.js file:

props.href = router.createHref(toLocation);

My code is pretty simple though:

import { Navbar, Nav, NavItem } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

const NavBarMenu = () => (
    <Navbar inverse collapseOnSelect>
        <Navbar.Header>
            <Navbar.Toggle/>
        </Navbar.Header>
        <Navbar.Collapse>
            <Nav>
                <LinkContainer to="/">
                    <NavItem>Home</NavItem>
                </LinkContainer>
                <LinkContainer to="/news">
                    <NavItem>News</NavItem>
                </LinkContainer>
                <LinkContainer to="/news">
                    <NavItem>Bio</NavItem>
                </LinkContainer>
            </Nav>
        </Navbar.Collapse>
    </Navbar>
)

The package versions Im using:

    "antd": "^3.8.1",
    "express": "^4.16.3",
    "next": "^6.1.1",
    "react": "^16.4.2",
    "react-bootstrap": "^0.32.1",
    "react-dom": "^16.0.0",
    "react-router-bootstrap": "^0.23.3",
    "react-router-dom": "^4.3.1",
    "styled-jsx": "^3.0.2"

Any ideas guys? @taion @fengerzh ?
Im pulling my hair since im facing this error.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexyaseen picture alexyaseen  路  3Comments

tomatau picture tomatau  路  3Comments

ackvf picture ackvf  路  3Comments

sarbbottam picture sarbbottam  路  3Comments

andrewpillar picture andrewpillar  路  3Comments