I am trying to do this for Example
<a class="link active">Home</a>
<a class="link">About</a>
<a class="link">Contact</a>
<a class="link">Work</a>
<a class="link">Support</a>
this is how my navigation is
<div className={cx(s.root, className)} role="navigation">
<Link className={cx(s.link, 'fa fa-dribbble')} to="/dribbble" />
<Link className={cx(s.link, 'fa fa-behance')} to="/behance" />
<Link className={cx(s.link, 'fa fa-linkedin')} to="/linkedin" />
<Link className={cx(s.link, 'fa fa-twitter')} to="/twitter" />
<Link className={cx(s.link, 'fa fa-instagram')} to="/instagram" />
<Link className={cx(s.link, 'fa fa-vimeo')} to="/vimeo" />
</div>
and this the Link Component code.
import React, { Component, PropTypes } from 'react';
import history from '../../core/history';
function isLeftClickEvent(event) {
return event.button === 0;
}
function isModifiedEvent(event) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
class Link extends Component {
constructor(props) {
super(props);
this.state = {active: false};
}
click() {
this.setState({active: true});
}
static propTypes = {
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
onClick: PropTypes.func
};
handleClick = (event) => {
let allowTransition = true;
if (this.props.onClick) {
this.props.onClick(event)
}
if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
return;
}
if (event.defaultPrevented === true) {
allowTransition = false;
}
event.preventDefault();
if (allowTransition) {
if (this.props.to) {
history.push(this.props.to);
} else {
history.push({
pathname: event.currentTarget.pathname,
search: event.currentTarget.search
});
}
}
};
render() {
const currentPath = this.to.getCurrentPathname();
const { to, ...props } = this.props; // eslint-disable-line no-use-before-define
return <a href={history.createHref(to)} id={this.state.path ? 'active' : null} {...props} onClick={this.handleClick} />;
}
}
export default Link;
Any Ideas?
Actually @elasim Gave me the answer. The answer is as follows:
import React, { PropTypes } from 'react';
import cx from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Navigation.css';
import Link from '../Link';
function Navigation({ className }) {
return (
<div className={cx(s.root, className)} role="navigation">
<Link className={cx(s.link, 'fa fa-dribbble', { 'active': location.pathname === '/dribbble' })} to="/dribbble" />
<Link className={cx(s.link, 'fa fa-behance', { 'active': location.pathname === '/behance' })} to="/behance" />
<Link className={cx(s.link, ' fa fa-linkedin', { 'active': location.pathname === '/linkedin' })} to="/linkedin" />
<Link className={cx(s.link, ' fa fa-twitter', { 'active': location.pathname === '/twitter' })} to="/twitter" />
<Link className={cx(s.link, ' fa fa-instagram', { 'active': location.pathname === '/instagram' })} to="/instagram" />
<Link className={cx(s.link, ' fa fa-vimeo', { 'active': location.pathname === '/vimeo' })} to="/vimeo" />
</div>
);
}
Navigation.propTypes = {
className: PropTypes.string
};
export default withStyles(s)(Navigation);
Note this is throwing me an error in the console
warning.js?85a7:44Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client)
Any Idea on how to solve that?
With history module v2.0+ I think there should be an API method history.getCurrentLocation().pathname which might be a better option to use than window.location.pathname, so it could work in isomorphic environment.
I didn't know that api. that's definitely better. use history.getCurrentLocation()
@koistya @elasim
I put it like this in this Boiler plate
import React, { PropTypes, Components } from 'react';
import cx from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Navigation.css';
import history from '../../core/history';
import Link from '../Link';
function Navigation({ className }) {
return (
<div className={cx(s.root, className)} role="navigation">
<Link className={cx(s.link, 'fa fa-dribbble', { 'active': history.getCurrentLocation().pathname === '/dribbble' })} to="/dribbble" />
<Link className={cx(s.link, 'fa fa-behance', { 'active': history.getCurrentLocation().pathname === '/behance' })} to="/behance" />
<Link className={cx(s.link, ' fa fa-linkedin', { 'active': history.getCurrentLocation().pathname === '/linkedin' })} to="/linkedin" />
<Link className={cx(s.link, ' fa fa-twitter', { 'active': history.getCurrentLocation().pathname === '/twitter' })} to="/twitter" />
<Link className={cx(s.link, ' fa fa-instagram', { 'active': history.getCurrentLocation().pathname === '/instagram' })} to="/instagram" />
<Link className={cx(s.link, ' fa fa-vimeo', { 'active': history.getCurrentLocation().pathname === '/vimeo' })} to="/vimeo" />
</div>
);
}
Navigation.propTypes = {
className: PropTypes.string
};
export default withStyles(s)(Navigation);
and is throwing an error
TypeError: _history2.default.getCurrentLocation is not a function
at Navigation (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:2784:3), <anonymous>:39:165)
at ReactCompositeComponentMixin._constructComponentWithoutOwner (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:5298:3), <anonymous>:294:27)
at ReactCompositeComponentMixin._constructComponent (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:5298:3), <anonymous>:262:21)
at ReactCompositeComponentMixin.mountComponent (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:5298:3), <anonymous>:181:21)
at Object.ReactReconciler.mountComponent (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:1170:3), <anonymous>:46:35)
at ReactCompositeComponentMixin.performInitialMount (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:5298:3), <anonymous>:357:34)
at ReactCompositeComponentMixin.mountComponent (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:5298:3), <anonymous>:244:21)
at Object.ReactReconciler.mountComponent (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:1170:3), <anonymous>:46:35)
at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:5454:3), <anonymous>:215:44)
at ReactDOMComponent.Mixin._createContentMarkup (eval at <anonymous> (http://localhost:3001/assets/main.js?329bbc8bb82cc3dee3ce:5316:3), <anonymous>:611:32)
@daiky00 see changelog:
3.0.0-0 (Mar 19, 2016)
- Added
history.getCurrentLocation()method
@frenzzy so this API only works in history module v 3.0? wow let me try that right now
@frenzzy, @elasim, @koistya
I install history module v 3.0.0 and is not throwing an error anymore but the active class is not been added to the link. So this API is not working apparently any feedback will be much appreciated.
Try logging the path somewhere to check if it's the same with what you're expecting (e.g. '/dribbble/' instead of '/dribbble'). Probably you should use regex to match path, because this won't work for '/dribbble/subpath'.
@arvigeus I already try that and the path is always '/' root
With Redux, I'm currently using this hack:
server.js
app.get('*', async (req, res, next) => {
...
store.dispatch(setRuntimeVariable({
name: 'currentPathname',
value: req.path,
}));
...
});
client.js
const removeHistoryListener = history.listen(location => {
if (currentLocation !== null) { // to prevent first-time calling
store.dispatch(setRuntimeVariable({
name: 'currentPathname',
value: location.pathname,
}));
}
....
});
MyComponent.js
....
export default connect(state => ({
pathname: state.runtime.currentPathname,
}))(withStyles(s)(MyComponent));
just sharing, hoping to find a more robust and redux-free method.
@daiky00 In reactjs, render() not work until you change state using setState() or prop on parent component
@elasim Yeah when I render I make sure I change the state before I console.log the element and I already knew that but thanks for the tip. I just change the whole structure of the project and use react router instead. I don't like the universal router currently in this boiler plate. React Router should be in this Boiler Plate +1 for that
I'm having a similar issue where adding a class on the client side causes a React checksum warning since the code doesn't match between client and server. history.getCurrentLocation().pathname always returns / on the server side, and the actual pathname on the client, i.e. /login. Can anyone point me in the right direction on when/how to add the class?
This is the message that I get in the browser:
warning.js?85a7:44Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) resentation" class="active" data-reactid
(server) resentation" class="" data-reactid="31">
Thanks for your help!
@rkait good catch! It might be better idea to initialize a new history object for each HTTP request inside server.js/app.get('*', ...) method (SSR). Also pass it as a context variable to React application, and access from inside React components via context, e.g. this.context.history.getCurrentLocation().pathname. A PR with this feature is welcome!
@rkait have you tried something like this?
const yourRoute = {
path: '/some-page',
action({ path }) {
return <div>Current Path: {path}</div>
}
}
or
const routes = {
path: '/',
async action({ path, next, render }) {
const component = await next();
if (component !== undefined) {
return render(<Layout currentPath={path}>{component}</Layout>);
}
},
children: [ /* Nested Routes Here */ ]
}
I ended up using the solution with redux from @awesomejerry above. Would love to know if there is a better solution in the future. Thanks!
I saw some workarounds, but why history.getCurrentLocation() doesn't return the right URL on the server side? What I should I do to get that right? (it would be nice if it's possible avoid using argument/parameter on the render components)
@kaitlynreese @jlVidal I needed to highlight the active navigation tab - might be overkill but I solved the issue by making a component that used state to track whether the current url path was active.
HeaderLink.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import Link from '../Link';
import s from './HeaderLink.css';
import history from '../../history';
class HeaderLink extends Component {
constructor(props) {
super(props);
this.state = {
isActive: false,
};
}
componentDidMount() {
this.onPath();
}
componentWillReceiveProps() {
this.onPath();
}
onPath() {
if (history.location.pathname.includes(this.props.to)) {
this.setState({ isActive: 'active' });
} else {
this.setState({ isActive: false });
}
}
render() {
return (
<Link
to={this.props.to}
className={cx(s.link, this.state.isActive && s.active)}
>
<span>{this.props.title}</span>
{this.state.isActive && <span className={s.activeAfterStyle} />}
</Link>
);
}
}
HeaderLink.propTypes = {
to: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
};
export default withStyles(s)(HeaderLink);
Navigation.js
import React, { Component } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import HeaderLink from './HeaderLink';
import s from './Navigation.css';
class Navigation extends Component {
render() {
return (
<nav className={s.root}>
<div className={s.navMenu}>
<div className={s.navPages}>
<HeaderLink title="Home" to="/" />
<HeaderLink title="About" to="/about" />
<HeaderLink title="Contact" to="/contact" />
</div>
</div>
</nav>
);
}
}
export default withStyles(s)(Navigation);
I got here looking for an answer to this problem, and after reading this thread I think I will just go for the custom link solution provided in the docs, and styling the nested link with a container class.
Use NavLink to specify active classname without any extra work.
like this
import { NavLink } from 'react-router-dom'
I used application context to get the current path of the router, since History is used only on the client side, the below code works on both server and client;
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import ApplicationContext from 'ApplicationContext';
import history from '../../history';
function isLeftClickEvent(event) {
return event.button === 0;
}
function isModifiedEvent(event) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
function handleClick(props, event) {
if (props.onClick) {
props.onClick(event);
}
if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
return;
}
if (event.defaultPrevented === true) {
return;
}
event.preventDefault();
history.push(props.to);
}
const Link = React.forwardRef((props, ref) => {
const { context } = useContext(ApplicationContext);
const { activeClassName, to, children, ...attrs } = props;
const isActive = context.pathname === to;
attrs.className = isActive
? `${attrs.className} ${activeClassName}`
: attrs.className;
return (
<a ref={ref} href={to} {...attrs} onClick={e => handleClick(props, e)}>
{children}
</a>
);
});
if (__DEV__) {
Link.displayName = 'Link';
}
Link.propTypes = {
activeClassName: PropTypes.string,
to: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func,
};
Link.defaultProps = {
activeClassName: 'active',
onClick: null,
};
export default Link;
Most helpful comment
Use NavLink to specify active classname without any extra work.
like this
import { NavLink } from 'react-router-dom'