React.js implement menu [highlight active link] - javascript

The following React.js code renders a navbar with two links named 'about' and 'project'. On page load the 'about' link is active and colored red. When the other link is clicked the state of the navbar is set to 'project', 'about' link style is set back, and 'project' is colored red.
I achieve this by attaching a click handler to both link tags, and set the state of active to the name of the event.target.innerHTML.
I'm new to react, and I feel this is a really verbose way of going about this. I am aware that there is an activeClassName prop you can pass to a react-router link, but I want a way that does not use it.
import React, { Component } from 'react'
import { Link, Route } from 'react-router'
export default class Navbar extends Component {
constructor() {
super();
this.state = {
active: 'about'
}
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
this.setState({
active: e.target.innerHTML
});
}
render() {
let aboutStyle;
let projectStyle;
if (this.state.active === 'about') {
aboutStyle = { color: '#ff3333' };
projectStyle = {};
} else {
aboutStyle = {};
projectStyle = { color: '#ff3333' };
}
return (
<div className='navbar'>
<Link to='/'><h2>BK //</h2></Link>
<div className='menu'>
<Link style={aboutStyle} onClick={this._handleClick} to='about'>about</Link>
<Link style={projectStyle} onClick={this._handleClick} to='projects'>projects</Link>
</div>
</div>
)
}
}

At this day you can use NavLink from react-router-dom. This object supports attributes as activeClassName, activeStyle, or isActive (for functions).
import { NavLink } from 'react-router-dom';
<NavLink to='about' activeClassName="active">about</NavLink>
// Or specifing active style
<NavLink to='about' activeStyle={{color: "red"}}>about</NavLink>
// If you use deep routes and you need an exact match
<NavLink exact to='about/subpath' activeClassName="active">about</NavLink>
For more options read documentation: https://reacttraining.com/react-router/web/api/NavLink

maybe slightly less verbose... in Pseudocode
const menuItems = [
'projects',
'about',
];
class MenuExample extends React.Component {
_handleClick(menuItem) {
this.setState({ active: menuItem });
}
render () {
const activeStyle = { color: '#ff3333' };
return (
<div className='menu'>
{menuItems.map(menuItem =>
<Link
style={this.state.active === menuItem ? activeStyle : {}}
onClick={this._handleClick.bind(this, menuItem)}
>
{menuItem}
</Link>
)}
</div>
);
}
}

Using NavLink in place of Link gives you the opportunity to set your preferred style for the active page/link.
So you can go ahead and set the style in CSS like this.
.active {
color: 'red'
}

We can use NavLink to highlight the active by writing custom class handling based on de-structured isActive property
<NavLink
to='/'
className={
useCallback(({isActive}) => isActive ? classes.active : classes.link)}
>
Home
</NavLink>
Ref: https://stackoverflow.com/a/72520423/4652706

As of React Router v6, activeClassName prop will not work. Find the solution here, ReactJS - Unknown prop `activeClassName` on <a> tag. Remove this prop from the element

Related

Display a single element of an array according to its ID REACT

I would like to make an articles system on my site created with React, but I'm a bit lost.
I have a JS array that allows me to make a loop to display my articles. When I click on an article, it sends me to the right page thanks to the <Link />. But here is my problem, how to display the content of the article I clicked on? I tried to get the slug of the article and to compare it to the ID of the elements of my array but I have to admit that I don't know where I stand.
Here is my code, it only returns a <h1>Title: { id }</h1> (which is the correct slug every time)
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams
} from "react-router-dom";
import '../styles/Single.css'
const posts = [
{
source:
"./images/esdexp-th-homepage.jpg",
content: {
name: "Expérience ESD",
link:"/post/ESDexp",
idSlug :"ESDexp"
}
},
{
source:
"./images/vacui-th-homepage.jpg",
content: {
name: "Horror Vacui",
link:"/post/horrorvacui",
idSlug : "horrorvacui"
}
},
{
source:
"./images/meteo-th-homepage.jpg",
name: "Forecast",
link:"/post/forecast",
idSlug :"forecast"
},
{
source:
"./images/ami-th-homepage.jpg",
name: "Citroën Ami",
link:"/post/ami",
idSlug :"ami"
},
{
source:
"./images/musee-th-homepage.jpg",
content: {
name: "Musée Home",
link:"/post/musee",
idSlug :"musee"
}
},
];
export default function ParamsExample() {
return (
<Router>
<Switch>
<Route path="/post/:id" children={<Child />} />
</Switch>
</Router>
);
}
function Child() {
let { id } = useParams();
console.log({id})
var itemPost = posts.filter(function(displayPost) {
return displayPost.idSlug == { id };
});
console.log(itemPost)
return (
<div>
<div className="single-container rev-block">
<div className="single-content-container">
<h1>Title : {id}</h1>
</div>
</div>
</div>
);
}
I hope I've made this as clear as possible,
Thanks in advance!
It is not working because the posts data is not consistent. The idSlug of some posts are inside the content object.
To handle that situation you have to check if the idSlug is not undefined, and if it is, set the post slug or ID to use the idSlug inside the content object.
A working example can be found here.
Sandbox: https://codesandbox.io/s/angry-cannon-7kxiz?file=/src/App.js
The useParams has been replaced with an actual ID or slug of a post because the sandbox does not allow routing.
This line handles the inconsistency of the posts data.
let post_slug = displayPost.idSlug
? displayPost.idSlug
: displayPost.content.idSlug;
Also, please note that filter returns another array, so you have to access post or the itemPost using the first index.
<h1>Title : {itemPost[0].content.name}</h1>
And of course, you can also use the id returned by the useParams.
try this.
function Child() {
let { id } = useParams();
console.log({id})
var itemPost = posts.filter(function(displayPost) {
return displayPost.idSlug === id ; // you don't need those curly braces.
});
console.log(itemPost)
return (
<div>
<div className="single-container rev-block">
<div className="single-content-container">
<h1>Title : {id}</h1>
</div>
</div>
</div>
);
}
Get the help of <Link /> to pass the state from parent component to child component. Make the parent component like this:
<Link to={{
pathname:{'/post/post.id'},
state:{
post:post
}
}}>
Then you can get the post in the child component as below:
function child(props){
const post = props.location.state.post
}
Also, try to make the routing as below. It won't make much complications:
<Route path="/post/:id" component={Child} />

convert React Functional Component into Class with Props

I was hoping to find some help with this issue I have been having converting a file into a class based Component. I've struggled with this one for awhile, and I just cannot get the onClick to work correctly. Everything else renders fine, but the click function simply does not work. I would greatly appreciate any tips, feedback, or help!
import * as React from 'react';
import "./bsi-tabs.scss"
import IBsiTabProps from "./ibsi-tab-props"
function Tab ({
onClick = function(){return; },
tabIndex = '',
tabID = ''
}: IBsiTabProps) {
return (
<li>
<a
onClick={(event) => {
event.preventDefault();
onClick(tabIndex);
}}
href="/#"
>
{tabID}
</a>
</li>
);
}
export default BsiTab;
my attempt at conversion :
import * as React from 'react';
import "./bsi-tabs.scss"
import IBsiTabProps from "./ibsi-tab-props"
/*
Credit https://gist.github.com/diegocasmo/5cd978e9c5695aefca0c6a8a19fa4c69 for original
js files, edited by Robert McDonnell to convert to typescript
*/
export class BsiTab extends React.Component<IBsiTabProps, any> {
onClick = function(){return; }
tabIndex = ''
tabID = ''
render(){
return (
<li>
<a
onClick={(event) => {
event.preventDefault();
this.onClick(this.props.tabIndex);
}}
href="/#"
>
{this.props.tabID}
</a>
</li>
);
}
}
export default BsiTab;
interface :
export default interface IBsiTabProps {
onClick ?: Function;
tabIndex ?: Number | string;
tabID: String;
children : React.ReactNode;
}

ReactJS - how to get current page in the URL?

I have a menu in my React app, here's the Menu component:
class Menu extends React.Component {
constructor(props) {
super(props);
this.state = {
active: '/'
}
}
render() {
return (
<nav>
<ul className='list-inline'>
<li>
<Link to='/' style={this.state.active === '/' ? activeStyle : {}} onClick={this.handleClick.bind(this, '/')}>
Home
</Link>
</li>
<li>
<Link to='/contact' style={this.state.active === 'contact' ? activeStyle : {}} onClick={this.handleClick.bind(this, 'contact')}>
Contact
</Link>
</li>
)
}
}
export default Menu
When I click on a menu item, the clicked item will get higlighted (bold). But if I have in the URL some page (eg. http://localhost:3001/contact) and refresh the page, this Contact item in the menu will not be highlighted (because highlighting is made based on the onClick event).
I tried to parse the content of URL with import * as qs from 'query-string'; // for fetching the URL paramaters and then I tried to display the URL content:
console.log(qs.parse(location.search));
But the output is only {} instead of the desired contact (so I could highlight this item in the menu).
Thus, when a URL is loaded straight (without click on the respective menu item), how do I catch this situation and highlight the respective menu item?
Thank you
React router has a component for exactly this situation
https://reacttraining.com/react-router/web/api/NavLink
You can change the component from Link to NavLink and add the component will check the url in which you are.
<Link to='/contact' style={this.state.active === 'contact' ? activeStyle : {}} onClick={this.handleClick.bind(this, 'contact')}>
Contact
</Link>
<NavLink
to="/contact"
activeStyle={activeStyle}
>Contact</NavLink>
Also, you can check the URL params from the router instance:
https://reacttraining.com/react-router/web/example/url-params

Vue.js: Add router-link-active to vue-router component loaded for root

I'm having some trouble with vue-router. My routes are set up like this:
const routes = [
{ path: '/', component: a },
{ path: '/a', component: a },
{ path: '/b', component: b },
{ path: '/c', component: c }
]
As you can see, I want to load component a when the page loads. But I don't want the URL to change to /a when the page loads. However I would like to trigger the router-link-active-class on the <router-link> related to the a component, even though I'm still at /.
I've tried with some simple JS like:
if(window.location.hash === '#/') {
var el = document.querySelector('#drawerList').firstElementChild;
el.className += 'router-link-active';
}
However vue is not removing the class again when I click on the link to /b or /c. Can any of you hint me in the corrent direction?
You can manually bind class to router-link like this
<router-link :class="{'router-link-active': $route.fullPath ==='/' || $route.fullPath === '/a'}" to="/a"></router-link>
The best solution is use "exact" in root url
<router-link to="/" tag="a" exact>Home</router-link>
Try <router-link to="/" exact>
It prevents active class inclusive match behavior.
This is my pre-answer - see solution instead.
I ended up doing something custom, rather than seeking a native solution. After looking at a few issues on GitHub, I realised that something like this, stupid as it is - I admit, doesn't exist natively in Vue.js.
My solution: adding an eventlistener that removes itself along with the class, once another link is clicked in the #drawerMenu:
HTML structure:
<ul id="drawerMenu">
<li>
<router-link to="/a">A</router-link>
</li>
<li>
<router-link to="/b">B</router-link>
</li>
<li>
<router-link to="/c">C</router-link>
</li>
</ul>
JS:
if(window.location.hash === '#/') {
var menu = document.querySelector('#drawerMenu');
var el = menu.firstElementChild.firstChild;
el.className += 'router-link-active';
var removeRouterClass = function () {
el.classList.remove('router-link-active');
menu.removeEventListener('click', removeRouterClass);
}
menu.addEventListener('click', removeRouterClass);
}
I hope this helps others like me, and hopefully this will be built into Vue natively at some point.
In my case I had a use-case when I needed to have an active class whenever 1 of the following is true:
/users
/users/1/edit
/users/1/view
using vuejs 3 in main app.vue I have routes defined. The routes are then parameter-injected into sidemenu component (which is not important in this example)
app.vue dependencies
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
setup() method (using composition API):
setup() {
const route = useRoute();
const myActiveClass = ref(null);
const checkIfMustActivateRoute = () => {
return {
active:
window.location.pathname.startsWith("/users/view/") ||
window.location.pathname.startsWith("/users/edit/"),
};
};
watch(
() => route.fullPath,
async (_) => {
myActiveClass.value = checkIfMustActivateRoute();
}
);
myActiveClass.value = checkIfMustActivateRoute();
const routes = ref([
{ id: 0, name: "Home", href: "/", icon: "fas fa-home" },
{
id: 1,
name: "Users",
href: "/users",
icon: "far fa-clone",
class: myActiveClass,
},
]);
return {
routes,
};
},
NavLinks are dynamically generated like so:
<li v-for="tab of routes" :key="tab.id" class="nav-item">
<router-link :to="tab.href" class="nav-link" :class="tab.class">
<div class="align-items-center">
<span class="nav-link-icon"><i :class="tab.icon"></i></span>
<span class="nav-link-text ms-3">{{ tab.name }}</span>
</div>
</router-link>
</li>
Now whenever the router's path property changes I trigger the active class update function. This way we don't have to use aliases or any other trickery.
The result is - for all 3 paths I always enable active class.
use could use exact-active-class on your router-link element.
Note: desiredClass is the class you want to apply if the URL matches the router-link's to attribute.
<router-link to="/about" exact-active-class="desiredClass">About</router-link>

Set active Bootstrap nav dropdown when any item in the list is active in React-Router v4

I'm using React Router v4 and trying to write up a wrapper that allows the Bootstrap dropdown class to be active. The gist is that I need match multiple routes, and I'm unsure exactly how to do that in this wrapper.
Here is what I have:
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
const DropDownLinkWrapper = ({links, label}) => {
return (
<Link to="/link1|/link2"> // <-- this is what I want to do
{({isActive}) =>
<li className={`dropdown ${isActive ? 'active' : ''}`}>
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{label}<span className="caret"/></a>
<ul className="dropdown-menu">
{// output menu links here}
</ul>
</li>
}
</Link>
);
};
DropDownLinkWrapper.propTypes = {
links: PropTypes.array.isRequired,
label: PropTypes.string.isRequired
};
export default DropDownLinkWrapper;
I would imagine that the first <Link/>'s to prop would need to match on two URLs. But of course, this doesn't work. Does anyone have a solution for this?

Categories