React. Basic example of re-rendering using react-router. +100 rep - reactjs

I'm newbie. And I'm newbie at React. I'm trying react-router. I saw this example: https://reacttraining.com/react-router/web/example/basic.
I reproduced it on Codesandbox.io here: https://codesandbox.io/s/7jxq0j6qp0
I don't understand why the Menu is re-rendering itself when I change URL using menu's links.
If you open console you can see it.
Why?
I think it should not re-render itself. Just the route section. Where am I wrong?

You can write your component as PureComponent. Since there is no prop changes right now it won't be re-rendered:
class Menu extends React.PureComponent {
render() {
console.log("Menu render() - ", Date.now());
return (
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
</div>
);
}
}
But, please be aware of the negative sides of this method for other use cases: Dealing with Update Blocking

Related

Link component error with multiple childe elements when there are none [duplicate]

I'm having a component called Nav inside components directory and it's code is some thing like below:
import Link from 'next/link';
const Nav = () => {
return(
<div>
<Link href="/"> <a> Home </a> </Link>
<Link href="/about"> <a> About </a> </Link>
</div>
)
}
export default Nav;
This gives me the error:
Error: React.Children.only expected to receive a single React element child.
But if I remove the <a> tags within <Link> components, I can view the pages, but then in the console I'm getting a warning of:
Warning: You're using a string directly inside <Link>. This usage has been deprecated. Please add an <a> tag as child of <Link>
So what am I doing wrong here?
This issue is due to the space between <Link> tag and <a> tag.
So change your code like,
<div>
<Link href="/"><a> Home </a></Link>
<Link href="/about"><a> About </a></Link>
</div>
Reason for the change:
-> The <Link> can have only one child node and here the space between the link and a tag are considered as a child nodes.
-> So there are two child nodes (One is space and another is <a> tag) which is invalid and hence such error occurs.
Space between < Link > and < a > makes you error "Unhandled Runtime Error
Error: Multiple children were passed to with href of / but only one child is supported https://nextjs.org/docs/messages/link-multiple-children
Open your browser's console to view the Component stack trace."
Remove the space and it work fine.
import Link from "next/link";
const NavBar = () => {
return (
<nav>
<Link href="/"><a>Home </a></Link>
<Link href="/About"><a>About </a></Link>
<Link href="/Contact"><a>Contact </a></Link>
</nav>
)
}
export default NavBar
I had the same issue as I was doing:
<Link href={`/tutorial/${tutorial.slug}`}>{tutorial.title}</Link>
The fix was to wrap it in an a tag:
<Link href={`/tutorial/${tutorial.slug}`}><a>{tutorial.title}</a></Link>
If the child is <a> tag then add legacyBehavior. it will work
import Link from 'next/link'
function Legacy() {
return (
<Link href="/about" legacyBehavior>
<a>About Us</a>
</Link>
)
}
export default Legacy
I had a space that was considered as an undefined component
<Link href={to}><a className={'abc'}>{children}</a> </Link>
Removing the space, it was ok. 30 minutes lost only, thanks to experience and internet help.
I was having the same issue there were no problems with spaces rather I was passing an integer inside Link
<Link href={{
pathname: `/dashboard/people`,
query: { idPerson: 123 }}}>
{123}
</Link>
I resolved this error by parsing the integer using toString() method
<Link href={{
pathname: `/dashboard/people`,
query: { idPerson: 123 }}}>
{(123).toString()}
</Link>
Check if inner content is empty
In addition to the scenarios covered by other answers, this error also gets fired when there's no content in between <Link> opening and closing tags.
For example:
<Link href='/'></Link> // i am an error
Im following the NextJs tutorial and space is not only the culprit. Semi colon also.
// without space, this will also not work because of the semi-colon
function FirstPost() {
return (
<>
<h1> First Post </h1>
<h2>
<Link href="/">
<a>Back to Home</a>;
</Link>
</h2>
</>
);
// with space but without semi-colon will not also work
function FirstPost() {
return (
<>
<h1> First Post </h1>
<h2>
<Link href="/">
<a> Back to Home </a>
</Link>
</h2>
</>
);
}

How do you set an 'active' class in React and CSS?

I have navigation in my React app. However I would like the colour of navigation to change if on that page.
Heres my code Ive added an active className to the items.
<Link to='/'>
<li className='menu-list-item menu-list-logo active'>Gatsby's</li>
</Link>
<Link to='/drink'>
<li className='menu-list-item active'>Drink</li>
</Link>
<Link to='/food'>
<li className='menu-list-item active'>Food</li>
</Link>
<Link to='contact'>
<li className='menu-list-item active'>Contact</li>
</Link>
The navigation links currently have a background of black. I want the colour to change to lets say red.
P.S I'm using standard CSS for styles.
There is actually a property of activeClassName in NavLink of react router.
It goes like this
Import { NavLink } from 'react-router-dom`
...
<NavLink exact activeClassName="your_active_css_classname" className="normal_css">Link name </NavLink>
Don't forget to add exact. Read more here: https://reacttraining.com/react-router/web/api/NavLink
Light/Fast way to do this would be to change the class at the parent DOM element of these <Link> elements... from there the CSS would do what it works best.
Assuming these <Link> ... </Link> tags are enclosed inside a <nav> ... </nav> tag, which is the parent DOM element.
{ (onThePage) ? <nav className="onThePageClass"> ... </nav> : <nav className="normalClass"> ... </nav> }
Try this else share MVCE if you get stuck so that it serves as a good example for others looking for help

How to push route onClick in list React

Here is the list which I am trying to display
<li key={data.url} onClick={this.props.history.push('/viewnetworkaccount')}>
{data.keyName}
</li>
I want to push data.url. How To do so.
You can do this way,
<li key={data.url} onClick={() => this.props.history.push('/viewnetworkaccount')}>
{data.keyName}
</li>
Also make sure you wrap your component using withRouter HOC.

Redux component, can I write click events without using class

I am writing the following redux component (dumb)
const TopMenuComponent = () => (
<div className={styles.topMenuIndex}>
<header role="banner">
<nav role='navigation'>
<ul>
<li className={styles.expando}>☰</li>
<li>Home</li>
<li>About</li>
</ul>
</nav>
</header>
</div>
);
Now on the click event of the ☰ link I want to write some code to perform some function. I have two questions around this
Is it possible to do this without deriving this from the React.Component class using the above code. My concern is that this is a dumb component and if we derive this from React.Component then we are adding more intelligence into this.
Should this expansion click trigger a stage change on the redux store? This is just a simple expansion of a bunch of links thats all
If you want to make it a dumb component, then you can pass in a property, e.g. isOpen, which defines if the menu is expanded or not, and a function, e.g. handleClick, which should be called on a button click. This way, the state can be managed by a parent component.
Now whether you manage this state in the internal state of parent component or with your redux store is entirely up to you. You should choose redux if you will need to open and close the menu remotely.
There are two ways to achieve what you want to without affecting the redux store
First, Passing function as a prop to the component from the parent component and then calling this onClick
const TopMenuComponent = (props) => {
return (
<div className={styles.topMenuIndex}>
<header role="banner">
<nav role='navigation'>
<ul>
<li className={styles.expando} onClick={props.myhandler}>☰</li>
<li>Home</li>
<li>About</li>
</ul>
</nav>
</header>
</div>
)
};
Second way is to define a onClick handler in the component itsef like
const TopMenuComponent = () => {
const myHandler = (e) => {console.log('clicked'); // some other code}
return (
<div className={styles.topMenuIndex}>
<header role="banner">
<nav role='navigation'>
<ul>
<li className={styles.expando} onClick={myhandler}>☰</li>
<li>Home</li>
<li>About</li>
</ul>
</nav>
</header>
</div>
)
};

How to pass a method to a child react component?

folks!
I'm starting to construct an app with webpack, react, redux, etc. And im not sure if im facing the situation as i should:
I want to develop a single page app, having a single react node "<myApp/>" that will contain everything inside. The first thing that will appear on my page, is a bootstrap navbar. What i want, is to create a react component that renders the navbar and to pass him 1) a list of strings, and 2) a function that should be called when one of that items is clicked. I want to do it in that way, because i would like to track at top level the "page" where i am (it will be always one of the navbar items).
Here my myApp component:
export default class myApp extends React.Component {
constructor(props) {
super(props);
this.handleNavbarClick = ::this.handleNavbarClick;
this.state = {
currentPage: 'Home',
navbarItems : ['Home', 'contact', 'etc']
};
}
handleNavbarClick(e) {
//debug alert:
alert(e);
this.state.currentPage = e;
}
render() {
return (
<div>
<NavBar items={this.state.navbarItems} itemClickHandler={this.handleNavbarClick}/>
<Wellcome />
<LandingMenu />
CurrentPage: {this.state.currentPage}
</div>
);
}
}
and here my navbar component:
export default class NavBar extends React.Component {
constructor(props) {
super(props);
}
render() {
var handler = this.props.itemClickHandler;
var itemsHtml = this.props.items.map(function(i) {
return <li className="nav-item"> <a className="nav-link" onClick={handler(i)}>{i}</a></li>
});
return (
<nav className="navbar navbar-static-top navbar-dark bg-inverse">
<a className="navbar-brand" href="#"><img src={LogoSrhSmall} /></a>
<ul className="nav navbar-nav">
{itemsHtml}
</ul>
</nav>
)
}
}
As it is, when i click one of the navbar items, nothing happens, but when the page loads, the alert of the function handleNavbarClick is automatically called for each item, and i have no idea why :(
Can somebody please tell me what im doing wrong? im facing the problem in the right way?
Thanks in advance,
John
#janaka-stevens, #Jamby, Thank you!
Applying some changes from your answers, i was able to get the name out by storing it on the attribute "id" and accessing it throw e.target.id.
For this case that's fine, but supose that instead of work with simple strings, i would like to work with an array of more complex objects, let's say:
[
{ name: 'home', url: '/someroute', importantData: {...} },
{ name: 'otherLink', url: '/someOtherroute' , importantData: {...} }
]
and i would like to pass the complete selected object to the parent, myApp?
At the moment, it looks like that:
on myApp:
...
handleNavbarClick(e) {
this.setState({currentPage : e.target.id}) ;
}
...
and on the NavBar:
...
renderNavItem(item) {
return (<li className="nav-item">
<a id={item} className="nav-link" onClick={this.props.itemClickHandler}>{item}</a>
</li>)
}
render() {
return (
<nav className="navbar navbar-static-top navbar-dark bg-inverse">
<a className="navbar-brand" href="#"><img src={LogoSrhSmall} /></a>
<ul className="nav navbar-nav">
{this.props.items.map(this.renderNavItem, this)}
</ul>
</nav>
)
}
...
but if on the <a> tag, instead of to use:
onClick={this.props.itemClickHandler}
i use:
onClick={this.props.itemClickHandler(item)}
the itemClickHandler is called on each render iteration, as react would "call" the method when iterating through the items to display them (without any user interaction).... any idea why?
Thanks in advance,
John
I'm going to suggest some changes to your code to make it more readible:
For the itemClickHandler you use something other than (e) because e usually means event. For that you should do itemClickHandler(page).
And then inside the Navbar component, you probably want to do something like this:
export default class NavBar extends React.Component {
constructor(props) {
super(props);
}
renderNavItem(item) {
<li className="nav-item">
<a className="nav-link" onClick={this.props.handleNavbarClick(item)}>{item}</a>
</li>
}
render() {
return (
<nav className="navbar navbar-static-top navbar-dark bg-inverse">
<a className="navbar-brand" href="#"><img src={LogoSrhSmall} /></a>
<ul className="nav navbar-nav">
{this.props.items.map(this.renderNavItem)}
</ul>
</nav>
)
}
}
BUT the main thing you're doing wrong is that you're NEVER setting state.
The #1 rule in React is you never want to alter a prop or a state (by doing this.prop.whatever = blar or this.state.whatever = blar. If you do that, then nothing will change and components you're passing props or state to may not have the correct information. So if you want to change state you must call this.setState({currentPage: page}).
So inside your handleNavbarClick you want to change it to:
handleNavbarClick(page) {
//debug alert:
alert(page);
this.setState({currentPage: page});
}
This will tell React that you've changed state and wish to re-render.
You have two problems. The first is the e in handleNavbarClick(e) is event. What you would want to do is have an identifier for each of your items list. Then you would get e.target.id. Of course you will also need to add the id in your mapped list. The second problem is you need to pass 'this' to your mapped list like so;
render() {
var itemsHtml = this.props.items.map(function(i) {
return <li className="nav-item"> <a id={i.id} className="nav-link" onClick={this.props.itemClickHandler}>{i}</a></li>
}, this);
Note the second parameter in map.
I had the problem where the handlers ran by themselves without actually clicking the buttons. I was calling the handler with an argument like you were are first. I removed that, and the behavior stopped. I tried the bind(null, props) trick and it worked for the props, but I didn't know how to access the event itself in the handler so I could e.preventDefault().
After looking for a minute, I found this gem, which iterates through all the unnamed arguments a function has received.
for (var i=0; i<arguments.length; i++) {
console.log(arguments[i])
};
Using this, I could access the event in the handler with
e = arguments[1]
and now all is well.
Thanks for giving me a starting point!

Resources