NavLink from ReactStrap has unexpected behavior - "to" and "activeClassName" not applying - javascript

There is a larger wrapping component, called Sidenav:
const Sidenav = () => (
<SidebarGroup>
<SidebarLink to={routes.example1.path} name="example1" />
<SidebarLink to={routes.example2.path} name="example2" />
</SidebarGroup>
);
And the component that I'm mostly concerned about:
const SidebarLink = ({ to, name }) => (
<li className={styles.sidebarlink}>
<NavLink to={to} activeClassName={styles.active}>{name}</NavLink>
</li>
);
I have 2 problems here.
1) Normally, to works perfectly to redirect to the destination URL. However, when I switched to using NavLink from ReactStrap vs. Link, to no longer works - href has to be used instead in order to correctly perform the click-through action. Other examples online of people using NavLink are all using to - is there some kind of dependency I'm missing?
2) I want to apply activeClassName to the menu item that is currently tabbed to or is being clicked on. Hypothetically, again, the syntax is correct but it is not working as anticipated - specifically, the .active class is not being applied when you click on or tab through the menu item in question. Again, is this a dependency error, or something else? withRouter was suggested in one place, but that did not make a difference.
Thank you in advance for any guidance!

Related

Navigate to # id in Layout component

I'm using Gatbsy and I'm trying to create an accessibility link for the users (using screen readers) to be able to navigate to content (skipping the navigation).
My layout component (which is used in every page of the website) looks something like this:
const Layout = ({ children }) => {
{/* hidden for brevity */}
return (
<>
Skip to content {/* <-reference line */}
{/* hidden for brevity */}
<main id="main-content">
{children}
</main>
{/* hidden for brevity */}
</>
);
}
Current behaviour:
As it's shown in the code example above the link will be generated once for the page I'm "landing on", and it will not update for subsequent pages I navigate to:
ex. landing on the 'blog' page my link will point to localhost:9000/blog#main-content and navigating to 'about' page will not make that section of the layout re-render (this is most likely normal behaviour).
I also tried using useRef, but got the referenced item being undefined during rendering.
What would be the way to complete this task (considering that the pages are server-side-rendered)? Is there a way I could do it without passing the full route path to use it inside the tag?
As I commented above, Gatsby's <Link> component extends from #reach/router (from React) doesn't allow navigating to any parameter. From the docs:
Neither <Link> nor navigate can be used for in-route navigation with a
hash or query parameter. If you need this behavior, you should either
use an anchor tag or import the #reach/router package—which Gatsby
already depends upon—to make use of its navigate function.
If you use navigate, for example, navigate("/blog#main-content") it will redirect to /blog, omitting the parameter since it's not allowed.
In your case, the useRef approach won't directly since at the moment you are creating it, it's not still rendered so, you can use the useRef hook approach alongside with useEffect hook, to ensure that the DOM tree is loaded or using some manual trigger:
const Layout = ({ children }) => {
const mainRef= useRef(null);
const navigateToContent= () => {
mainRef.current.scrollIntoView(); //manual trigger
}
useEffect(()=>{
mainRef.current.scrollIntoView(); //automatic trigger
}, [])
{/* hidden for brevity */}
return (
<>
<div onClick={navigateToContent}>Skip to content</div>
{/* hidden for brevity */}
<main id="main-content" ref={mainRef}>
{children}
</main>
{/* hidden for brevity */}
</>
);
}
In the snippet above I've added two different approaches, choose the one that fits your requeriements. The key part is to set correctly the reference of the main tag, initially set as null to avoid rehydration issues when the routing changes.
The manual trigger, just call a function (navigateToContent) in your Skip to content
element that uses the scrollIntoView() built-in function to scroll to the reference.
The automatic trigger, just uses the same idea but the function is triggered once the DOM tree is loaded (useEffect with empty deps, []).
The issue has been solved as following:
Actually changing this <Link to="/#main-content">Skip to content</Link> to this <Link to="#main-content">Skip to content</Link>
(removing the /) works perfectly fine

How to solve "Invalid values for prop `propmethod` on <li> tag." when using SubMenu in another component

I am using SubMenu in another Component as I require State for checkboxes inside Menu. I get this warning when I am passing a method as props to that custom Component I made.
This is the whole warning:
Invalid values for props additem, selectallzones on li tag.
Either remove them from the element, or pass a string or number value
to keep them in the DOM. For details, see
(link)
My parent Component looks like this, keep in mind addItem() and selectAllZones() are in this component (I have simplified the code a bit so it only has things that are in question):
<SubMenu key="City" title="City">
{this.state.cities.map(city => <SubCity key={city.city} city={city} additem={this.addItem} selectallzones={this.selectAllZones}></SubCity>)}
</SubMenu>
And my SubCity Component looks like this:
<SubMenu {...this.props} key={this.props.city.city} title={this.props.city.city}>
{this.props.city.zone.map(zone =>
<Menu.Item key={zone.id}><Checkbox disabled={this.state.checkedAll} onChange={(e) => this.props.additem('zones', zone.zone, e)}>{`Zone ${zone.zone}`}</Checkbox></Menu.Item>)}
</SubMenu>
I added the {...this.props} in SubMenu component of SubCity looking at another StackOverflow question for other errors I was getting, and I assume this is what is causing the problem. But I can't remove that or my code doesn't run at all. Is there any way I can do this without any warnings in the console? (The code does work, only get a warning in the console.)

React 16.9.0 "javascript:;" href alternative? [duplicate]

This question already has answers here:
In React, how can I cause anchors to do nothing on click?
(17 answers)
Closed 3 years ago.
After updating to React 16.9.0 I'm getting big warnings like this:
Warning: A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed "javascript:;".
It comes from code like this:
const Component = ({someAction}) => (
<a href="javascript:void(0)" onClick={someAction}>click me</a>
);
Looking at the StackOverflow question about which “href” value should I use for JavaScript links in HTML it seems most of you agree javascript:void(0) is the best option, which will be no longer possible in React 16.9.0.
Just replacing with href="#" is problematic, since the browser will scroll to the top of the page and change the displayed URL. Especially if you use hash-links for routing this is very problematic.
I could update my whole codebase to have e.preventDefault(); in each and every event handler, but this seems hard to do, especially when the event handlers are automatically created from Redux action creators or hooks. I do not look for the answer "just include e.preventDefault();" everywhere!
Also using a <button> means I have to deal with lots of unwanted styles applied.
So I was wondering: Are there any solutions specific to the library React to get a working <a> link that just triggers an action without side effects? I want to change the code as little as possible and get rid of deprecation warnings.
In React blog post:
URLs starting with javascript: are a dangerous attack surface
because it’s easy to accidentally include unsanitized output in a tag
like <a href> and create a security hole.
In React 16.9, this pattern continues to work, but it will log a
warning. If you use javascript: URLs for logic, try to use React event
handlers instead.
I personally prefer to use it like this:
<a href="#!" onClick={clickAction}>Link</a>
I came up with a component like this:
import React, {useCallback} from 'react';
function AHrefJavascript({ children, onClick, ...props }) {
const handleClick = useCallback(
e => {
e.preventDefault();
return onClick(e);
},
[onClick]
);
return (
<a href="#javascript" {...props} onClick={handleClick}>
{children}
</a>
);
}
It will just wrap the event handler to create a new one that also calls e.preventDefault(). It will add a hash link to href which does not trigger the deprecation warning. Using the power of hooks it only changes the event handler, when the passed in handler is updated. One problem with this that in the browser you can still open this link in a new tab, so it is not quite as good as a link with a javascript:void(0), which would prevent that.
It is easy to use, the component looks like this:
const Component = ({someAction}) => (
<AHrefJavascript onClick={someAction}>click me</a>
);
So probably one could replace a href="javascript:void(0)" with AHrefJavascript across the project and then just add an import for the component everywhere.
In the end maybe the best option is to use a <button> and go through the hoops to remove all the unwanted styles.

react link vs a tag and arrow function

I just started on react router.
I have two questions. What is the difference between using <Link to="/page"> and <a href="page">? Both make the exact same get request to /page but I get an error when I use <a href="page"> but it works when I use <Link to="/page"> when I am nesting routes. I don't understand, how there could be any difference, when I know for fact that both render to exact same url?
Second is the weird arrow function in react router v4 documentation
const About = () => (
<div>
<h2>About</h2>
</div>
)
I know () => {} these are new in ES6 but I cannot find anything on normal brackets instead of parentheses. What are they?
Edit
My index.js class (I have all the imports)
render((
<Router>
<div>
<Route component={App}/>
</div>
</Router>
), document.getElementById('root')
);
My App.js class
class App extends Component {
render() {
return (
<div className="container">
<header>
<span className="icn-logo"><i className="material-icons">code</i></span>
<ul className="main-nav">
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/teachers">Teachers</Link></li>
<li><Link to="/courses">Courses</Link></li>
</ul>
</header>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/teachers" component={Teachers}/>
<Route path="/courses" component={Course}/>
</div>
);
}
}
export default App;
The error I'm getting.
Cannot GET /about on the browser when I try to move to localhost:8080/about. However, when I click the about button, it goes to exactly the same url /about and renders perfectly
This may be a bit late to address your issue and you may well have figured it out. But here's my take:
First:
What is the difference between using <Link to="/page"> and <a
href="page">
On the surface, you seem to be comparing apples and oranges here. The path in your anchor tag is a relative path while that one in the Link is absolute (rightly so, I don't think react-router supports relative paths yet). The problem this creates is say you are on /blah, while clicking on your Link will go to /page, clicking on the <a href='page' /> will take you to /blah/page. This may not be an issue though since you confirmed the correctness of the url, but thought to note.
A bit deeper difference, which is just an addon to #Dennis answer (and the docs he pointed to), is when you are already in a route that matches what the Link points to. Say we are currently on /page and the Link points to /page or even /page/:id, this won't trigger a full page refresh while an <a /> tag naturally will. See issue on Github.
A fix I used to solve my little need around this was to pass in a state property into link like so <Link to={{pathname: "/page", state: "desiredState"}}>Page</Link>. Then I can check for this in the target component's (say <Page />) componentWillReceiveProps like so:
componentWillReceiveProps(nextProps){
if (nextProps.location.state === 'desiredState') {
// do stuffs
}
}
Second question:
the weird arrow function in react router v4 documentation... I cannot find anything on normal brackets instead of parentheses. What are they?
Arrow functions; again #Dennis and #Jaromanda X have kind of addressed it. However, I've got three bits to add:
When you have () => blah without the curly braces {}, you are implicitly returning whatever follows the => in this case blah. But when you have curly braces immediately after the arrow, then it's now your responsibility to return something if you so desire. So () => blah (which by the way is synonymous to () => (blah)) will be more similar to () => { return blah } and not () => { blah }.
So what happens if you want to return an object: { blah: blah }; this is what #Jaromanda X was pointing at. You will then need to do () => ({ blah: blah }) or simply () => ({ blah }) for implicit return or you could return explicitly like so () => { return { blah: blah } }.
My third bit is to point you to MDN
Hope it helps.
The href attribute would trigger a page refresh which would reset the application states. However the link and navlink of react-router doesn't trigger a page refresh. Since React is used to create single page applications most of the time make sure you choose Link or Navlink when working with routing
The component allows you to do more than the normal link element. For instance, because it's a React component you have the benefits of having a state and what not (if you want that). You can see more documentation on here. Without the error I'm not sure what happens, but I suspect the routing library wants you to use the component, over a normal html element.
With regards to () => {} this is a construct which is called an anonymous function, or a lambda expression. It's basically the same as saving a function in a variable: var x = function(){ return (<div>...) }; if you have anything in the first parenthesis, it's a parameter which you have access to: const x = (y) => return y*2; The reason it's done in React is to expose the function scope to the component it lies in.
There is no better then looking at the code source.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js
You can see that Link is a component, that internally use history. Which is the module|library behind the history and navigation for react-router. And come with different modes (in memory history, browserHistory, hashHistory. And even custom).
Yea as a similarity it render an anchor tag but the default behavior is overridden (preventDefault()). They could have used just a div. But not completely right. As for the reason bellow.
So basically it work like that:
Observe the condition bellow
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
(!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
}
if the condition above is met. It will use history (push or replace). Otherwise it will leave the browser normal behavior. And so in that case it will be just a normal anchor tag <a />. Example letting the browser handle target='blank'. The condition are well explained.
Then depending on the type of history object. The behavior change. Not the behavior of ` itself. But just the result of the history object type.
In resume:
<Link /> is a component, that render a <a /> anchor tag. However in the main conditions the default behavior is prevented (preventDefault()). That allow it to apply the change to the history object (onClick event). Which react-router navigation is based on. And on the some conditions as mentioned above. It just fall back to the browser behavior. And just be exactly a <a /> anchor tag (no preventDefault()).
For the use. If you are using React-router. Then you just need to use Link.

Material-UI component in React Router Link triggering the link

I use material-ui components in react with react-router. I have a problem when I want to display list-items that are supposed to work as link elements, but also contain a submenu inside that should not trigger the parent link. It does and I don't know how to disable it.
var iconMenu =
<IconMenu iconButtonElement={<IconButton><MoreVertIcon /></IconButton>}>
<MenuItem primaryText='change name' onTouchTap={this.edit}/>
<MenuItem primaryText='delete' onTouchTap={this.delete} />
</IconMenu>
<ListItem
key={i}
containerElement={<Link to={`/items/${item.id}`} />}
rightIconButton={iconMenu}
/>
When I click the iconMenu button, I do not want the <Link to={`/items/${item.id}`} /> to be triggered, so that I stay in the page. But it does. So how can I fix this problem? I tried to add event handler to run stopPropagation() but it was not successful...
Thanks!
For React Router v4, add
onTouchTap={() => this.props.history.push(`/items/${item.id}`)}
to the ListItem, instead of containerElement.
Use import { withRouter } from 'react-router-dom' and export default withRouter(Foo) to add history to the component's props.
First of all, I'd like to admit that I do not like material-ui and thus do not recommend it to people who consider starting a project with it. The reasoning behind is that you sacrifice too much time customising the components to your needs - a solution that is opposite to the idea of React. It also uses inline styles that you always have to overwrite in the component file, not in your scss or less. This sucks big time. I don't even mention all the UI interaction actions that are handled with JS that could make your performance ache.
Another short mention is to the react-router. Unfortunately I'm not a fan of it either. Guys, why do you change the API in every next release? Why is it so damn difficult to just reset the location queries? Just look at FlowRouter and see how fantastic a route API should be implemented.
Anyways, my solution was to implement a wrapper around the <Link /> component and move the <IconMenu /> outside of the <Link /> wrapper:
<li key={i}>
<ListItem
key={i}
containerElement={<Link to={`/items/${item.id}`} />}
/>
{iconMenu}
</li>

Categories