How can I use the react-router Link component with the fabric Nav Component? There is a issue #733 that may be related to this, but I am not sure.
I cannot use renderLink
to render the Link
component as that results in a nested hyperlink.
Here's a customized example of the Nav:
http://codepen.io/dzearing/pen/ZBGNQg?editors=0010
Take a look at onRenderLink
usage. It doesn't perfectly mix into Link
but it should get you going. You can totally override the content within each link and render whatever you want.
Actually while this works to render custom content, it does not replace the anchor element, even though it's called onRenderLink. How confusing.
I think this is a bug. It should be rendering the NavLink by default, but should allow you to override that.
Given that react-router is pretty much essential for any SPA with react, I'd also like to see official support for this in office ui fabric. Until then, here's how I am using it with the Nav...
Step One: Modify the groups object
For every link or group inside your 'groups' remove all href tags and add a new custom tag containing the destination you want to send to react-router:
{ name: 'Supervisors', key: 'key4', link: '/administration/supervisors' }
Step Two: Add an event to the Nav tag
In the definition add a handler to the Nav's onLinkClick property:
<Nav
groups={groups}
onLinkClick={this.onClick.bind(this)}
/>
Step Three: Add the event handler to your component
Add your custom event handler to the react component (as a sibling/peer to the render method), using the function name from Step Two and the property name from Step One - onClick and link were what I used, but feel free to use whatever you want.
Import the browserHistory component:
import { browserHistory } from 'react-router';
Then add the event handler function:
onClick(event, element) {
browserHistory.push(element.link);
}
I found the programmatic way to navigate from this SO question:
http://stackoverflow.com/questions/31079081/programmatically-navigate-using-react-router
@graham-sportsmgmt you saved my day!
Actually I suggest to standardize all Nav items into button
instead of a
. Because all of this can be simplified using:
const groups = [
{
links:
[
{ name: 'Dashboard', onClick: () => browserHistory.push('/dashboard'), key: '/dashboard' },
]
}
];
BUT the onClick
itself considered as buggy, since when we applied onClick
, it render the link into button
instead of a
AND that makes styling went wrong.
That will happend when we use onClick
. I don't know is it general bug or just happened on me.
I had this issue today as well, except with the ContextualMenu and Breadcrumb components.
ContextualMenu has IContextualMenuItem.onRender
, similar to Nav.onRenderLink
which was mentioned earlier in this thread. Breadcrumb has no way to customize the rendering of the links.
I haven't tested if the one from ContextualMenu has the same bug as Nav.onRenderLink
, but regardless: I think it's not going to get us fully there yet.
If I would replace the default link markup with the Link
component from React-Router, I will lose the Fabric styling on the link. So I think we need a way to combine the two.
I noticed that Semantic-UI-React has the following convention for rendering their components as a certain element or component: <ComponentName as="customElement">
or <ComponentName as={CustomComponent}>
. (For example, have a look at: http://react.semantic-ui.com/elements/button)
Based on the use cases that I'm seeing here, I'd suggest that we need two separate solutions:
Link
from Fabric as a custom component (perhaps with <Link as={CustomComponent}>
). If all the props get transferred to the component that is rendered, we could easily wire together the Fabric Link and React-Router Link.<SomethingProvider something={myCustomizedThing}>
component, that is placed either at the root of the app or a more specific scope within the app.As for a quick fix, mine was the following:
import {routerShape} from 'react-router';
import {Breadcrumb} from 'office-ui-fabric-react/lib/Breadcrumb';
class ExampleComponent extends Component {
render() {
<Breadcrumb
items={[
{
key: 'foo',
text: 'Foo',
href: router.createHref('/bar'),
onClick: (e) => {e.preventDefault(); router.push('/bar')}
}
]}
/>
}
}
ExampleComponent.contextTypes = {
router: routerShape
};
Note that I am setting both a href
and onClick
. The href
is there to make sure that, in addition to instructing the router, it can also still behave like a regular link (e.g. open it in a new tab or window).
@graham-sportsmgmt Why not just add event.preventDefault()
?
onClick(event, element) {
event.preventDefault();
browserHistory.push(element.url);
}
Instead of changing url to link props ?
Guys (specially from Microsoft and other maintainers of Fabric), did anyone got a solution for that?
The workarounds provided here brings a lot of side effects specially if you are using react-router
v4.
Thanks!
No solution ever found, and I agree that the issues start to pile up with any workaround.
We figured that MS isn't putting a high priority on this project so we moved our project to a different UI framework.
@graham-sportsmgmt thanks for the reply. May I ask you which framework did you moved to?
We moved to Angular 4 and Bootstrap 4. We also looked at Zurb Foundation, Ionic, and the new Google Material, but Bootstrap had the widest set of components and the best Angular support.
I'm generating navigation this way:
import { Link } from 'react-router-dom'
const menuItems = [
{
key: 'Dashboard',
name: <Link to="/">Home</Link>,
icon: 'ViewDashboard'
}
@jlanio Works but unfortunately it messes up the CSS and the ability to click the button, you just can click the text (default a tag behavior), still not the perfect work around
With react-router v4 and SSR, I make a Nav this way:
import { withRouter } from 'react-router-dom';
export class SideNav extends React.Component<ISideNavProps, any> {
navWithRouter = withRouter(({ history }) => {
return (
<Nav
groups={...}
onLinkClick={
(ev?: React.MouseEvent<HTMLElement>, item?: INavLink) => {
if (item && item.key) {
history.push(item.key);
}
}}
selectedKey={history.location.pathname}
></Nav>);
});
render() {
return (
<div>
<this.navWithRouter />
</div>
);
}
}
It works well
@vertonghenb you are right,but we can achieve it this way:
export const routeConfig = [
{
path: '/',
name: '鍥剧墖绠$悊',
component: ImagesPanel,
exact: true,
hide: true
},
{
name: '鍥剧墖绠$悊',
path: '/images',
component: ImagesPanel
},
{
name: '鐢ㄦ埛淇℃伅',
path: '/profile',
component: ProfilePanel
}
];
const ParseGroup = (route: any) => {
return {
name: route.name,
key: route.path,
url: route.path // make Nav element render link as a <a/>
};
}
export class SideNav extends React.Component<ISideNavProps, any> {
navWithRouter = withRouter(({ history }) => {
return (<Nav groups={
[{
links: this.props.routes.filter((r: any) => !r.hide).map(ParseGroup)
}]}
onLinkClick={
(ev?: React.MouseEvent<HTMLElement>, item?: INavLink) => {
if (ev) {
ev.nativeEvent.preventDefault();
}
if (item && item.key) {
history.push(item.key);
}
}
}
selectedKey={
history.location.pathname
}
></Nav>);
});
render() {
return (<div className='sidebar'> <this.navWithRouter /> </div>);
}
}
when a url is set, Fabric will render Nav's link as a , so that we can open it in a new tab
Could a new component be written for this purpose ? or is it easier to adopt this component to use Links?
Here is my Solution, Altho this feels like a short sided way of doing things, it works, Ideally We could have a nav component that wraps a bunch of Links and lets the Links handle the logic ..
Also How can I change the SelectedKey?
| 2 import * as React from 'react';
| 1 import { Nav, INavProps } from 'office-ui-fabric-react/lib/Nav';
+|3 import { withRouter } from 'react-router-dom'
| 1
| 2 class RightNavStub extends React.Component<any, any> {
| 3 constructor(props: INavProps) {
| 4 super(props);
| 5 this._onClickHandler = this._onClickHandler.bind(this);
| 6 }
| 7
| 8 render() {
| 9 return (
| 10 <div className='nav'>
| 11 <Nav
| 12 groups={
+| 13 [
| 14 {
| 15 links:
| 16 [
| 17 { name: 'Home', url: '/', key: 'key1', onClick: this._onClickHandler},
| 18 { name: 'Login', url: '/login', key: 'key2', onClick: this._onClickHandler},
| 19 { name: 'Logout', url: '/logout', key: 'key3', onClick: this._onClickHandler},
| 20 { name: 'Wishlist', url: '/wishlist', key: 'key4', onClick: this._onClickHandler},
| 21 { name: 'Products', url: '/products/1', key: 'key5', onClick: this._onClickHandler},
| 22 ]
| 23 }
~| 24 ]
~| 25 }
~| 26 expandedStateText={ 'expanded' }
| 27 collapsedStateText={ 'collapsed' }
~| 28 selectedKey={ 'key1' }
| 29 />
| 30 </div>
| 31 );
| 32 }
| 33
| 34 _onClickHandler(e: React.MouseEvent<HTMLElement>) {
| 35 e.preventDefault()
| 36 console.log(e.currentTarget.pathname)
| 37 this.props.history.push(e.currentTarget.pathname);
| 38 return false;
| 39 }
| 40 }
| 41
| 42 export const RightNav = withRouter(RightNavStub)
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions to Fabric React!
I would like to update this thread, terribly sorry about the delay, but we are dedicating resources on cleaning up things like this, and will refine the set of components to address customization like this.
We are planning on apply an "as" pattern across all Fabric components :
To replace rendering a component with another implementation, you would could pass in "renderAs" value, which can be a component (functional or class based), or a string representing the new primitive root type. (Note that we tried to just use "as" but turns out it's already an html attribute type, so for components which mix in html attributes into the root element, that would conflict.)
For example:
<Link renderAs={ReactRouterLink} ... />
or:
<Link renderAs={ linkProps => (<SomethingDifferent url={ linkProps.href } />) } />
When a component renders multiple types of children, we use the "as" pattern but apply the part name for the parameter. For example "fooAs" would render a give type for the "foo" child.
So, we are planning some updates here:
Breadcrumb
: will have items
array, but also itemAs
for rendering the crumbs and dividerAs
for rendering the dividers.
Nav
: will have items
and itemAs
.
ContextualMenu
will have items
and itemsAs
.
We would like things to be as consistent as possible.
The first related PR which updates the Link
component is already submitted and linked above. (#4639)
For Nav
specifically, we really need to refactor it. We have this refactor going in an experiment currently and will graduate it to the main package in major release. (7.0 targeted currently.)
@dzearing whoops sorry, didn't read your last paragraph right.
FWIW - Here is how I got this working:
import React from 'react'
import { withRouter } from 'react-router-dom';
import { Nav } from 'office-ui-fabric-react/lib/Nav'
const FabricNav = withRouter(({ history }) => (
<Nav
onLinkClick={(event, element) => {
event.preventDefault();
history.push(element.url);
}}
groups={[
{
links: [
{
name: 'Home',
url: '/',
key: 'home',
},
{
name: 'Foo',
url: '/foo',
key: 'foo',
},
{
name: 'Bar',
url: '/bar',
key: 'bar',
},
]
}
]}
/>
));
export class LeftNav extends React.Component {
render() {
return (
<div className="left-nav">
<FabricNav />
</div>
)
}
}
@mj1856 It tells me You should not use <Route> outside a <Router>
. How do I solve this?
I use connected-react-router
.
index.js
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
...
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
rootElement
);
App.js
export default () => (
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/page" component={Page} />
</Switch>
</Layout>
);
Layout.js
export default function Layout(props) {
return (
<div class="ms-Fabric">
<div class="ms-Grid-row">
<div class="ms-Grid-col ms-lg2">
<Navigation />
</div>
<div class="ms-Grid-col ms-lg10">{props.children}</div>
</div>
</div>
);
}
I'm trying an experiment to use the suggested 'as' property with react router. It's not pretty, but it does provide routing, and the class concerns from fabric:
https://gist.github.com/lancegliser/78aa1f05bab599fe9cd12a63e6949e0a
So I don't know why this issue is closed.
The linkAs works very well as you can see in this example.
However it does not render the NavLink with the correct css styling that the FabricUI Nav uses, so it looks terrible. Font is different and indenting is non-existent. We still need to see an actual example where Fabric UI Nav renders traditional react NavLinks which work with the Router, and looks identical. Currently if you don't override linkAs it just redirects to different urls which is not how SPA websites work with react.
Most helpful comment
FWIW - Here is how I got this working: