The question is rather simple and is not specifically connected to navigation bars, it's just a good example..
I want to create simple navigation bar with active tab highlighting by using 'active' class. Currently I have two components: NavBar and NavItem. On tab click, I would like to make it active and make sure all others become inactive.
What would be the easiest solution to this? How to reset multiple NavItems inside NavBar to default inactive state without one that was clicked?
NavBar.js
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.menuItems = [
{ name: 'HOME', url: '#' },
{ name: 'NEWS', url: '#' },
{ name: 'CONTACT', url: '#' }
];
}
render() {
return (
<nav>
<ul>
CHOOSE:
{this.menuItems.map( (item,i) => <NavItem key={i} data={item} /> )}
</ul>
</nav>
);
}
}
NavItem.js
import React from 'react';
export default class NavItem extends React.Component {
constructor() {
super();
this.state = {
active: false
}
}
handleOnClick() {
this.setState({active: !this.state.active});
}
render() {
let item = this.props.data;
return <li><a href={item.url} className={this.state.active ? 'active' : ''} onClick={ (e) => this.handleOnClick(e) }>{item.name}</a></li>;
}
}
I don't see benefit of putting list item in a different component. In fact you would end up setting active class on 1 list item due to each item is a separate component.
Simplest thing could be to put list in the same component and pass an index in click function.
{this.menuItems.map((item,i) => <li><a href={item.url} className={this.state.activeItem == i ? 'active' : ''} onClick = {(e) => this.handleOnClick(e,i) }>{item.name}</a></li> )}
now click function would look something like -
handleOnClick = (event, index) => {
this.setState({activeItem: index});
}
So your component would look like
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.menuItems = [
{
name: 'HOME',
url: '#'
}, {
name: 'NEWS',
url: '#'
}, {
name: 'CONTACT',
url: '#'
}
];
this.state = {
activeItem: 0
}
}
handleOnClick = (event, index) => {
this.setState({activeItem: index});
}
render() {
return (
<nav>
<ul>
CHOOSE: {this
.menuItems
.map((item, i) => <li>
<a
href={item.url}
className={this.state.activeItem == i
? 'active'
: ''}
onClick=
{(e) => this.handleOnClick(e,i) }>{item.name}</a>
</li>)}
</ul>
</nav>
);
}
}
I've setup item at 0 index default active but you could change it according to the needs.
Looks like the React Docs article, Lifting State Up , has been written exactly to the use-case you have been demonstrated.
You are managing the isActive state inside each component in isolation. Each Instance of NavItem can't know, and should not know, what is the state of the other instances of NavItem
So ... lift your state up! Manage the information about active/inactive bars, inside Navbar, and control your multiple NavItem instances through props.
It should look like this:
NavBar.js
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.state = {
menuItems: [
{ name: 'HOME', url: '#', isActive: true },
{ name: 'NEWS', url: '#', isActive: false },
{ name: 'CONTACT', url: '#', isActive: false }
]
};
this.menuItems = [];
}
onNavItemClick = itemName => {
const nextMenuItems = this.state.menuItems.map(item => {
if (item.name === itemName) {
return { ...item, isActive: true };
}
return { ...item, isActive: false };
});
this.setState({
menuItems: nextMenuItems
});
};
render() {
return (
<nav>
<ul>
CHOOSE:
{this.state.menuItems.map((item, i) => (
<NavItem
key={item.name}
name={item.name}
isActive={item.isActive}
onClick={this.onNavItemClick}
url={item.url}
/>
))}
</ul>
</nav>
);
}
}
NavItem.js
import React from 'react';
export default class NavItem extends React.Component {
constructor() {
super()
console.log()
this.onClickFunction = () => this.props.onClick(this.props.name);
}
render() {
let item = this.props.data;
return (
<li className={this.props.isActive ? 'active' : ''}>
<a
href={this.props.url}
onClick={this.onClickFunction}
>
{this.props.name}``
</a>
</li>
);
}
}
Maybe you can use <NavLink to="" activeStyle={{color: 'red'}} /> component as links/tabs (from 'react-router-dom')
LiranC, Infinity, thanks for help and examples, I think I got it.
I knew it would end with lifting state to NavBar, only wasn't so sure how to make a reference to specific element..
I want to have only one element active at the time, with list item component for future purposes, so I mixed it and came up with such code. Looking forward to any further optimization advices.
NavBar.js
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.menuItems = [
{ name: 'HOME', url: '#' },
{ name: 'NEWS', url: '#' },
{ name: 'CONTACT', url: '#' }
];
this.state = {
activeItem: null
}
}
handleOnClick = (e,i) => {
this.setState({activeItem: i});
}
render() {
return (
<nav>
<ul>
CHOOSE:
{this.menuItems.map( (item,i) =>
<NavItem
key={i}
idn={i}
data={item}
isActive={this.state.activeItem == i ? true : false}
onClick={this.handleOnClick} />
)}
</ul>
</nav>
);
}
}
NavItem.js
import React from 'react';
export default class NavItem extends React.Component {
render() {
let item = this.props.data;
return (
<li>
<a
href={item.url}
className={this.props.isActive ? 'active' : ''}
onClick={ (e) => this.props.onClick(e, this.props.idn) }>{item.name}
</a>
</li>
);
}
}
Related
I have a Tabbar in my Tabbar Component, Which I Change the index props in it :
class Tabbar extends Component {
state = {
index: this.props.index,
name: this.props.name,
image: this.props.image
};
changeTabs = () => {
this.setState({index: this.props.index});
}
render() {
return (
<React.Fragment>
<div id={this.state.index} className="col">
<button onClick={this.changeTabs}></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And Then In my Other Component, I Wanna Re-Render a fragment after props change. Here's my Code :
import Tabbar from './Tabbar';
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
{index: 0, name: "tab0", image:require('../Assets/profile.svg'),childView: {ProfilePage} },
{index: 1, name: "tab1", image:require('../Assets/home.svg'),childView: {HomePage}},
{index: 2, name: "tab2", image:require('../Assets/blog.svg'),childView: {BlogPage}},
],
}
}
handleRender = () => {
this.state.tabs.map(item => {
if (item.index === this.props.index) {
return <item.childView/>;
}
})
return <BlogPage/>;
}
render() {
return (
<div>
<Header/>
{this.handleRender()}
{this.state.tabs.map(item =>
<Tabbar key={item.index} index={item.index} name={item.name} image={item.image}/>
)}
</div>
);
}
}
export default Tabview;
The Method "handleRender" should handle the rendering.
I tried to use "componentDidMount" or "componentDidUpdate", But I didn't work.
How Can I Make it Work?
Thank you in advance!
You dont need to have a state in the child component for this reason
You can simply have a callback in parent and call it in child component like below.
import React, { Component } from "react";
class Tabbar extends Component {
render() {
return (
<React.Fragment>
<div id={this.props.index} className="col">
<button
onClick={() => this.props.changeTabs(this.props.index)}
></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And in parent you maintain the active index state
import Tabbar from "./Tabbar";
import React, { Component } from "react";
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
//your tabs
],
activeIndex: 0
};
}
handleRender = () => {
this.state.tabs.map((item) => {
if (item.index === this.state.activeIndex) {
return <item.childView />;
}
});
return <div />;
};
render() {
return (
<div>
{this.handleRender()}
{this.state.tabs.map((item) => (
<Tabbar
key={item.index}
index={item.index}
name={item.name}
image={item.image}
changeTabs={(index) => this.setState({ activeIndex: index })}
/>
))}
</div>
);
}
}
export default Tabview;
Learning react and using semantic-ui-react Menu to build a SideBarMenu , and have below Questions
How does clicking on a Menu.Item trigger parent component (SBMenu) render ?
like render on component is triggered either due to change in state or props , but what is changing for SBMenu ?
Why is SBMenu render function called twice ?
Rendering .... future
Rendering .... future
SidebarMenu
import React from 'react';
import { Sidebar, Menu, Segment, Icon } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { withRouter } from 'react-router-dom';
import SBMenu from './SBMenu'
import './SidebarMenu.css';
import SBRoutes from './SBRoutes'
const menuItems = [
{ icon: 'dashboard', label: 'Future ITEM', name: 'future', url: '/future' },
{ icon: 'dashboard', label: 'Future ITEM1', name: 'future', url: '/future1' }
];
class SidebarMenu extends React.Component {
constructor(props) {
super(props);
this.handleItemClick = this.handleItemClick.bind(this);
}
handleItemClick () {
console.log('item clicked')
}
/*shouldComponentUpdate() {
return true;
}*/
render() {
console.log("SidebarMenu called....")
return (
<Sidebar.Pushable as={Segment} className="SidebarMenu">
<Sidebar
as={Menu}
borderless
animation="push"
icon="labeled"
inverted
onHide={this.handleSidebarHide}
vertical
visible={true}
width="thin"
>
<SBMenu menuItems={menuItems} />
</Sidebar>
<Sidebar.Pusher>
<div className="container">
<SBRoutes />
</div>
</Sidebar.Pusher>
</Sidebar.Pushable>
);
}
}
export default SidebarMenu;
SBMenu
class SBMenu extends React.Component {
constructor(props) {
super(props);
}
render() {
const {location,menuItems} = this.props;
console.log("Rendering .... " + location.pathname)
return (menuItems.map((item, index) => (
<Menu.Item
name={item.name}
as={Link}
to={item.url}
active={location.pathname === item.url}
data-menu={item.label}
key={`menu_item_${index}`}
>
<Icon name={item.icon} />
{item.label}
</Menu.Item>
))
);
}
}
export default withRouter(SBMenu);
You can change child state from parent using lifting state up. You can simply pass a method to the SBMenu and work with it.
SBMenu render twice because you use the same name in menuItems.
It should be working fine now if you change menuItems name.
I created a project in Reactjs where I have a list of names that I want to display in a custom list. Each item has a button to delete the item, however whenever I click the button, the last item is removed from the list no matter which list item I click.
I have already tried to debug my code using the js-console but that made the problem even stranger since the console displays the correct state wheras the component "List" renders a list item which is no longer present in the state object
import React, { Component } from 'react';
import './ListItem'
import ListItem from './ListItem';
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{name: 'Tobi'},
{name: 'Maxi'},
{name: 'David'},
{name: 'Peter'},
]
}
}
removeItem = (id) => {
let few = this.state.items;
few.splice(id,1);
//console.log(this.state.items);
this.setState({items: few}, function(){
console.log(this.state.items.map((item) => item.name));
this.forceUpdate();
});
}
render() {
return (
<div>
<ul>
{this.state.items.map((item, i) => <ListItem name={item.name} key={i} id={i} remove={this.removeItem}/>)}
</ul>
</div>
);
}
}
import React, { Component } from 'react';
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name,
id: this.props.id
}
}
test = () => {
this.props.remove(this.state.id);
}
render() {
return (
<li>{this.state.name} <button onClick={() => this.test()}>click me</button></li>
);
}
}
export default ListItem;
As is said i excpected the right list item to be removed however it is always the last item that isnt rendered anymore even though the state object says different.
The main problem is that you're using an array index as a key. When you first render the ListItems you have :
ListItem name={'Tobi'} key={0}
ListItem name={'Maxi'} key={1}
ListItem name={'David'} key={2}
ListItem name={'Peter'} key={3}
Let's say you removed the item with index 1, all other items will shift index:
ListItem name={'Tobi'} key={0}
ListItem name={'David'} key={1}
ListItem name={'Peter'} key={2}
React will only compare the keys, and because the only difference between the first and second render is that the item with key={3} is not present, this is the item that will be removed from the dom.
Also avoid mutating the state directly (few.splice(id,1)), and try to avoid this.forceUpdate()
Try using an actual id in your data :
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, name: "Tobi" },
{ id: 2, name: "Maxi" },
{ id: 3, name: "David" },
{ id: 4, name: "Peter" }
]
};
}
removeItem = id => {
let few = this.state.items.filter(item => item.id !==id);
//console.log(this.state.items);
this.setState({ items: few }, function() {
console.log(this.state.items.map(item => item.name));
//this.forceUpdate();
});
};
render() {
return (
<div>
<ul>
{this.state.items.map((item, i) => (
<ListItem
name={item.name}
key={item.id}
id={item.id}
remove={this.removeItem}
/>
))}
</ul>
</div>
);
}
}
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name,
id: this.props.id
};
}
test = () => {
this.props.remove(this.state.id);
};
render() {
return (
<li>
{this.state.name} <button onClick={() => this.test()}>click me</button>
</li>
);
}
}
function App() {
return (
<div className="App">
<List />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I'm having problem with sliding menu in just one item.
When I click on the config button every item shows menu. I tried to figure out something by passing props {mail.id} but I'm afraid I don't understand this.
I would like to have sliding menu just in one item -- the clicked one.
This is ConfigButton
import React, { Component } from "react";
import './Menu.css';
class ConfigButton extends Component {
render() {
return (
<button className="configButton"
onClick={this.props.onClick}
>
<i className="configButtonIcon fas fa-cog"></i>
</button>
);
}
}
export default ConfigButton;
And this is the Component which renders:
import React, { Component } from 'react';
import { NavLink, HashRouter } from 'react-router-dom';
import axios from 'axios';
import Menu from './Menu';
import ConfigButton from './ConfigButton';
const API = myAPI;
const navLinkStyle = {
textDecoration: 'none',
color: '#123e57'
};
class Emails extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
mails: []
};
this.handleMouseDown = this.handleMouseDown.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
}
handleMouseDown(e) {
this.toggleMenu();
e.stopPropagation();
}
toggleMenu() {
this.setState({
visible: !this.state.visible
});
}
componentDidMount() {
axios.get(API)
.then(response => {
const mails = response.data;
this.setState({ mails });
})
}
truncate = (text, chars = 140) =>
text.length < chars ? text : (text.slice(0, chars) + '...')
render() {
let mails = this.state.mails;
console.log(mails);
mails = mails.map(mail => {
return (
<div key={mail.id}>
<div className='mail'>
{
!mails.displayed
? <i className="notDisplayed fas fa-circle"></i>
: <i className="displayed far fa-circle"></i>
}
<HashRouter>
<NavLink
to={`/openemail/${mail.id}`}
style={navLinkStyle}
>
<ul className='ulMailWrap'>
<div className='mailHeader'>
<li>{mail.sender}</li>
<li>{mail.created}</li>
</div>
<li>{mail.subject}</li>
<li>{this.truncate(mail.message)}</li>
</ul>
</NavLink>
</HashRouter>
<ConfigButton onClick={this.handleMouseDown} />
<Menu handleMouseDown={this.handleMouseDown}
menuVisibility={this.state.visible}
/>
</div>
</div>
)
});
return (
<div>
{ mails }
</div>
);
}
}
export default Emails;
You can pass a function that will send a different parameter to the handler, depending on value of each element in the array.
Do something like this:
...
<div key={mail.id} onClick={() => this.handleOpenMenu(mail.id)}>
...
Then at the handler:
handleOpenMenu = id => {
// do different stuffs on the id you get here
this.setState({ visibleMenuId: id });
}
And then change the props you are passing to your menu component:
<Menu menuVisibility={this.state.visibleMenuId === mail.id} />
I have to render several tabs on my dashboard, where I can navigate between them. Each tab must be reusable. In my sample code, I have 2 tabs and if I click on a tab, the matching tab renders. I just don't know if I am reasoning in the right way. How can I do this? My current Code is this:
import React, {Component} from 'react';
function SelectTab(props) {
var tabs = ['Overview', 'Favorites'];
return (
<ul>
{tabs.map(function(tab){
return (
<li key={tab} onClick={props.onSelect.bind(null, tab)}>
{tab}
</li>
)
}, this)}
</ul>
)
}
function Content(props) {
return (
<div>Content {props.tab}</div>
)
}
export default class MainContent extends Component {
constructor(props){
super(props);
this.state = {
selectedTab: 'Overview'
}
this.updateTab = this.updateTab.bind(this);
}
componentDidMount() {
this.updateTab(this.state.selectedTab);
}
updateTab(tab){
this.setState(function(){
return {
selectedTab: tab
}
});
}
render(){
return (
<div>
<SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>
<Content tab={this.state.selectedTab}/>
</div>
)
}
}
You did almost good. But I would rather render Content in SelectTab component as its already getting selectedTab prop, this way you can render specific content based on that prop. Also:
componentDidMount() {
this.updateTab(this.state.selectedTab);
}
is not necessary as state is already set.
Refactored example:
import React, { Component } from 'react';
function SelectTab(props) {
var tabs = ['Overview', 'Favorites'];
return (
<div>
<ul>
{tabs.map(function (tab) {
return (
<li key={tab} onClick={props.onSelect.bind(null, tab)}>
{tab}
</li>
)
}, this)}
</ul>
<Content tab={props.selectedTab}/>
</div>
)
}
function Content(props) {
return <div>Content {props.tab}</div>
}
export default class MainContent extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'Overview'
};
this.updateTab = this.updateTab.bind(this);
}
updateTab(tab) {
this.setState(function () {
return {
selectedTab: tab
}
});
}
render() {
return <SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>;
}
}
Also keep in mind that with proper babel-transpiling your code could be much simplified like this:
import React, { Component } from 'react';
const TABS = ['Overview', 'Favorites'];
const SelectTab = ({ selectedTab, onSelect }) => (
<div>
<ul>
{
TABS.map(
tab => <li key={tab} onClick={() => onSelect(tab)}> {tab} </li>
)
}
</ul>
<Content tab={selectedTab}/>
</div>
);
const Content = ({ tab }) => <div>Content {tab}</div>;
export default class MainContent extends Component {
state = {
selectedTab: 'Overview'
};
updateTab = tab => this.setState(() => ({ selectedTab: tab }));
render() {
return <SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>;
}
}