Skip to content

React Router v4 #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
corydeppen opened this issue Oct 9, 2016 · 34 comments
Closed

React Router v4 #186

corydeppen opened this issue Oct 9, 2016 · 34 comments

Comments

@corydeppen
Copy link

While I understand it isn't stable yet, I was curious if there have been any discussions whether and when v4 of the Router would be supported.

A simple example like the following results in an error with the message router.isActive is not a function in ReactRouterBootstrap.js.

<LinkContainer to="/about">
  <NavItem eventKey={1}>About</NavItem>
</LinkContainer>
@taion
Copy link
Member

taion commented Oct 10, 2016

I'm pretty sure the v4 link supports equivalent functionality out-of-the-box.

@taion taion closed this as completed Oct 10, 2016
@corydeppen
Copy link
Author

This is the pen I used to test this. I haven't seen this work with v4 yet, though I could be doing something wrong in this simple example.

@jquense jquense reopened this Oct 11, 2016
@jquense
Copy link
Member

jquense commented Oct 11, 2016

does look like the API changed, no router.isActive

@taion
Copy link
Member

taion commented Oct 11, 2016

@giodamelio
Copy link

In the mean time I came up with this. It's pretty basic and it doesn't support query strings, but it works.

import React, { PropTypes } from 'react';
import { Link } from 'react-router';

function RouterLink({ to, children, activeOnlyWhenExact }) {
  return (
    <Link to={to} activeOnlyWhenExact={activeOnlyWhenExact}>
      {({ isActive, onClick, href }) =>
        <li className={isActive ? 'active' : ''}>
          <a href={href} onClick={onClick}>
            {children}
          </a>
        </li>
      }
    </Link>
  );
}

RouterLink.propTypes = {
  to: PropTypes.string.isReqired,
  children: PropTypes.object.isRequired,
  activeOnlyWhenExact: PropTypes.boolean,
};

export default RouterLink;

Then you can use it like this

<Nav>
  <RouterLink to="/" activeOnlyWhenExact>Home</RouterLink>
  <RouterLink to="/test">Test</RouterLink>
</Nav>

@Guigoz
Copy link

Guigoz commented Nov 16, 2016

Hello.
Thank you for your work on react router bootstrap!

Do you have any news on this issue?

@mmahalwy
Copy link

mmahalwy commented Dec 6, 2016

@giodamelio your propTypes are wrong with spelling mistakes. Should be:

{
  to: PropTypes.string.isRequired,
  children: PropTypes.node.isRequired,
  activeOnlyWhenExact: PropTypes.bool,
}

@csillag
Copy link

csillag commented Dec 19, 2016

@taion the code you referenced earlier (in the Link component) has been removed since that.
So right now I'm not sure how to provide this functionality. Can we reopen this issue?

@mmahalwy
Copy link

@csillag see @giodamelio's way of doing it. Works for me but I changed it up a little:

import React, { PropTypes } from 'react';
import Link from 'react-router/Link';

const LinkContainer = ({ to, children, activeOnlyWhenExact }) => (
  <Link to={to} activeOnlyWhenExact={activeOnlyWhenExact}>
    {({ onClick, href }) =>
      React.cloneElement(children, {
        onClick,
        href
      })
    }
  </Link>
);

LinkContainer.propTypes = {
  to: PropTypes.string.isRequired,
  children: PropTypes.node.isRequired,
  activeOnlyWhenExact: PropTypes.bool,
};

export default LinkContainer;

@v12
Copy link
Member

v12 commented Dec 22, 2016

@giodamelio @mmahalwy thanks for sharing your solution! The only thing you need to make sure it works properly with collapsible Navbar is to pass rest props of root element down to the children that is being wrapped (particularly, we are interested in onSelect, however, to make sure that everything else works, if there are any changes, we pass all of them down).

In essence, the whole react-router-bootstrap is replicated by the following code:

import React, { cloneElement, PropTypes } from 'react'
import { Link } from 'react-router'

export const LinkContainer = ({
  to,
  activeStyle,
  activeClassName,
  activeOnlyWhenExact = false,
  children,
  ...props
}) => <Link {...{ to, activeStyle, activeClassName, activeOnlyWhenExact }}>
  {
    ({ isActive: active, onClick, href }) =>
      cloneElement(
        React.Children.only(children),
        { onClick, href, active, ...props }
      )
  }
</Link>
LinkContainer.propTypes = {
  children: PropTypes.element.isRequired,
  to: PropTypes.string.isRequired,
  activeStyle: PropTypes.object,
  activeClassName: PropTypes.string,
  activeOnlyWhenExact: PropTypes.bool
}

export const IndexLinkContainer = (props) =>
  <LinkContainer {...props} activeOnlyWhenExact={true} />
IndexLinkContainer.propTypes = LinkContainer.propTypes

@xuefengwang
Copy link

Is this resolved? I'm facing same problem when integrating with react-router v4 and the code provided above doesn't work for me. Here is my code based on solution by @v12

import React, {Component} from 'react';
import {BrowserRouter, Match, Link} from 'react-router';
import {LinkContainer} from './LinkContainer';
import {Nav, Navbar} from 'react-bootstrap';
import './App.css';

const Download = () =>
  <div>Download</div>;

const Order = () =>
  <div>Order</div>;

class App extends Component {

  render() {
    return (
      <BrowserRouter>
        <div>
          <Nav>
            <LinkContainer to="/download">Download</LinkContainer>
            <LinkContainer to="/order">Order</LinkContainer>
          </Nav>
          <Match pattern="/download" component={Download}/>
          <Match pattern="/order" component={Order}/>
        </div>
      </BrowserRouter>
    );
  }
}

export default App;

@taion
Copy link
Member

taion commented Jan 19, 2017

It's a constant moving target. I don't think there's much we can do at this point.

@juliaogris
Copy link

Another hack for react-router-dom 4.0.0-beta.6:

import { NavLink } from 'react-router-dom'

function RouterLink ({ to, children }) {
  // use activeStyle from bootstrap.css of your theme
  // search for:  .navbar-default .navbar-nav > .active > a,
  return (
    <li>
      <NavLink to={to} activeStyle={{ color: '#d9230f' }}>{children}</NavLink>
    </li>)
}

<Nav>
  <RouterLink to='/home'>Home</RouterLink>
  <RouterLink to='/about'>About</RouterLink>
</Nav>

@websash
Copy link

websash commented Mar 12, 2017

Come up with ListItemLink.js (making react-router-bootstrap obsolete for me) Actually it's from the doc https://reacttraining.com/react-router/core/api/Route/children-func

import React from 'react'
import {Route, Link} from 'react-router-dom'

const ListItemLink = ({to, children}) => (
  <Route path={to} children={({match}) => (
    <li role="presentation" className={match ? 'active' : ''}>
      <Link to={to}>{children}</Link>
    </li>
  )} />
)

export default ListItemLink

Usage:

<Nav bsStyle="tabs">
    <ListItemLink to="/users">Users</ListItemLink>
    <ListItemLink to="/account">Account</ListItemLink>
</Nav>

@clabas
Copy link

clabas commented Mar 14, 2017

@websash I've tried your example and looked at the documentation, but I'm not able to get this approach to work. It seems that the children function is only ever called once on initial render. Perhaps there's something I'm missing; do you have a working example of this?

@ghost
Copy link

ghost commented Mar 15, 2017

I can't get this to work properly for list items within the pattern <Navbar><Navbar.Collapse><NavDropdown><NavItem>.

Of the three requirements:

  • correct appearance
  • change route when item clicked
  • close menu when item clicked

I can get two out of the three quite easily, but never all three at the same time. I've tried variations of all the suggested code posted here. Does anyone have something like this fully working?

@clabas
Copy link

clabas commented Mar 15, 2017

@websash I was able to figure out my problems with the solution you mentioned using the approach here: remix-run/react-router#4671 (comment).

It seemed that, in my case, using a PureComponent was preventing the component from rerendering.

@Siyfion
Copy link

Siyfion commented Mar 20, 2017

I believe I'm right in saying that v4 has now officially been released, so @taion you should have a much more static target to aim at now.

@camsjams
Copy link

Yes would like to see this working with React Router v4 as well.

@MHarris021
Copy link

MHarris021 commented Mar 21, 2017

+1 for @camsjams desire as well

@JamesTCS
Copy link

Just wanted to share my solution for now. Bootstrap already handles active links just fine and you can push new paths into the history to change routes. So you just need to wire it up with onClick. Take a look at my handleLink function and where it is called.

import React from 'react';
import {Navbar, Nav, NavItem, NavDropdown, MenuItem} from 'react-bootstrap';
import {Route} from 'react-router-dom';

import Home from './home';
import Playground from './playground';
import NewPage from './new_page';
import FarPage from './far_page';
import DropPage from './drop_page';


export default class Header extends React.Component {
    
     constructor(props) {
         super(props);
         props.history.push("/home");
         
         this.handleLink = this.handleLink.bind(this);
        
     }
    
    handleLink(path) {
        this.props.history.push(path);
    }
    
    render() {
        return(
            <div id="examples" className="container">
                <Navbar inverse fluid collapseOnSelect>
                    <Navbar.Header>
                        <Navbar.Brand onClick={()=>this.handleLink("home")}>React Bootstrap Examples</Navbar.Brand>
                        <Navbar.Toggle />
                    </Navbar.Header>
                    <Navbar.Collapse>
                        <Nav>
                            <NavItem eventKey={2} onClick={()=>this.handleLink("new_page")}>New Page</NavItem>
                            <NavItem eventKey={2} onClick={()=>this.handleLink("playground")}>Playground</NavItem>
                            <NavDropdown eventKey={3} title="Dropdown" id="basic-nav-dropdown">
                              <MenuItem eventKey={3.1} onClick={()=>this.handleLink("drop_page")}>Drop Down Link</MenuItem>
                              <MenuItem eventKey={3.2}>Another action</MenuItem>
                              <MenuItem eventKey={3.3}>Something else here</MenuItem>
                              <MenuItem divider />
                              <MenuItem eventKey={3.3}>Separated link</MenuItem>
                            </NavDropdown>
                        </Nav>
                        <Nav pullRight>
                            
                            <NavItem eventKey={2} onClick={()=>this.handleLink("far_page")}>Far Page</NavItem>
                        </Nav>
                    </Navbar.Collapse>
                </Navbar>
                <Route path="/home" exact component={Home}/>
                <Route path="/playground" component={Playground}/>
                <Route path="/new_page" component={NewPage}/>
                <Route path="/far_page" component={FarPage}/>
                <Route path="/drop_page" component={DropPage}/>
            </div>    
        );
    }
}

@miltonmc
Copy link

miltonmc commented Apr 8, 2017

@JamesTCS your solution is great and I'm trying to follow your example but with no success, because I couldn't figure out where the this.props.history is coming from. Can you clarify that for me?

@TrejGun
Copy link

TrejGun commented Apr 8, 2017

@miltonmc from here

@ashtianicode
Copy link

@miltonmc the link you give is about withRouter. how have you used it here? I didn't get it.

@miltonmc
Copy link

miltonmc commented Apr 9, 2017

@ashtianicode I've created a new component to wrap the NavItem:

import React from 'react';
import { withRouter  } from 'react-router-dom';
import {  NavItem } from 'react-bootstrap';

class NavItemWithoutRouter extends React.Component {
 
 constructor(props) {
         super(props);
         this.handleLink = this.handleLink.bind(this);
     }
    
    handleLink(path) {
        this.props.history.push(path);
    }

    render() {
        const { to, eventKey, children, onSelect } = this.props;
        return (
            <NavItem eventKey={eventKey} onSelect={onSelect} onClick={()=>this.handleLink(to)}>
                {children}
            </NavItem>
        );
    }
}

const RouterNavItem = withRouter(NavItemWithoutRouter)
export default RouterNavItem;

and then I've used it like this:

<Navbar>      
  <Navbar.Collapse>      
    <Nav pullRight>
      <RouterNavItem eventKey={1} to="/company">Company</RouterNavItem>
      <RouterNavItem eventKey={2} to="/team">Team</RouterNavItem>
      <RouterNavItem eventKey={3} to="/services">Services</RouterNavItem>
      <RouterNavItem eventKey={4} to="/contact">Contact</RouterNavItem>
    </Nav>
  </Navbar.Collapse>
</Navbar>

@bvn13
Copy link

bvn13 commented Apr 10, 2017

@miltonmc, works fine but <RouterNavItem to="/">Home</RouterNavItem> is always active

@miltonmc
Copy link

@bvn13, did you set the property exact on your route?

<Route path="/" exact component={Home} />

https://reacttraining.com/react-router/web/api/Route/exact-bool

@bvn13
Copy link

bvn13 commented Apr 10, 2017

@miltonmc , sure I try this key

@miltonmc
Copy link

miltonmc commented Apr 11, 2017

I did a work around on the code above adding the active property to the NavItem and it is working fine for me.

@bvn13 please, try the following work around and let me know if it worked for you:

render() {
    const { to, eventKey, children, onSelect, location } = this.props;
    return (
        <NavItem eventKey={eventKey} active={location.pathname === to} onSelect={onSelect} onClick={()=>this.handleLink(to)}>
            {children}
        </NavItem>
    );
}

@barrymichaeldoyle
Copy link

@miltonmc Thanks this solution works well, could you by chance give an implementation for MenuItem ??

@bvn13
Copy link

bvn13 commented Apr 11, 2017

@miltonmc Home menu button does not active now, but it couses an error when I click it:

Uncaught TypeError: _this2.handleLink is not a function

Seems it is not the issue, because I am going to use
<Navbar.Header> <Navbar.Brand> <a href="#">Mail Sender</a> </Navbar.Brand> <Navbar.Toggle /> </Navbar.Header>

instead of separate button.

@azpwnz
Copy link

azpwnz commented Apr 13, 2017

Hi @miltonmc
Thank you for your solution!
It works for me except one problem. Links only have active class in case if I click on them. However when page reloaded, they are not active.

In my case the reason was that 'to' was the same as 'location.pathname', but without '/' at the end.
Probably browser add that last '/' to url.
I've came with such change to active condition:
active={location.pathname === to || location.pathname === to + '/'}

It'll be great if you can provide the better solution.

@v12
Copy link
Member

v12 commented Apr 15, 2017

Hooray! RRv4 support has landed in v0.24.0 🎉

@react-bootstrap/publishers can we get this published to npm please?

@v12 v12 closed this as completed Apr 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests