import React, { Component } from 'react';
import axios from 'axios';
import DropdownMenu from './DropdownMenu';
class Navigation extends Component {
state = {
topCategory: []
}
componentDidMount() {
axios.get('http://localhost:3030/topCategory')
.then(res => {
console.log(res.data.express.catalogGroupView);
this.setState({
topCategory: res.data.express.catalogGroupView
})
})
}
render() {
const { topCategory } = this.state;
const navList = topCategory.map(navList => {
return (
<DropdownMenu>
<li key={navList.uniqueID}> <button onClick={this.showMenu}>{navList.name}</button></li>
</DropdownMenu>
)
})
return (
<div>
<ul className="header">{navList}</ul>
</div>
)
}
}
export default Navigation;
import React, { Component } from 'react';
class DropdownMenu extends Component {
constructor() {
super();
this.state = {
showMenu: false,
};
this.showMenu = this.showMenu.bind(this);
this.closeMenu = this.closeMenu.bind(this);
}
showMenu(event) {
event.preventDefault();
this.setState({ showMenu: true }, () => {
document.addEventListener('click', this.closeMenu);
});
}
closeMenu() {
this.setState({ showMenu: false }, () => {
document.removeEventListener('click', this.closeMenu);
});
}
render() {
return (
<div>
{
this.state.showMenu
? (
<div className="menu">
<button> Menu item 1 </button>
<button> Menu item 2 </button>
<button> Menu item 3 </button>
</div>
)
: (
null
)
}
</div>
);
}
}
export default DropdownMenu;
I'm new to react and have created a navigation menu in react js. On clicking on the nav items a dropdown should appear. But in my case, it's not working, mainly the dropdown part.Can someone please guide me on this. I have tried numerous methods, but it seems not to be working. If someone could some help or atleast show me, I would be very much be grateful
I see couple of issues in your code.
In Navigation component I see the onClick is this.showMenu but there is no such function bound to that component.
You might need to read and understand how state and props work.
Hope the below snippet helps.
class DropdownMenu extends React.Component {
constructor() {
super();
this.state = {
};
}
render() {
return (
<div>
{
this.props.showMenu
? (
<div className="menu">
<button> Menu item 1 </button>
<button> Menu item 2 </button>
<button> Menu item 3 </button>
</div>
)
: (
null
)
}
</div>
);
}
}
class Navigation extends React.Component {
state = {
topCategory: []
}
componentDidMount() {
this.setState({
topCategory: [{uniqueID:1000,name:'Nav Item 1'},{uniqueID:1001,name:'Nav Item 2'},{uniqueID:1002,name:'Nav Item 3'}]
})
}
render() {
const { topCategory } = this.state;
const navList = topCategory.map(navListItem => {
return (
<li key={navListItem.uniqueID}> <button onClick={(e) => (this.setState({selected:e.target.innerText}))}>{navListItem.name}</button>
<DropdownMenu showMenu={(()=>{
return this.state.selected === navListItem.name})()}/></li>
)
})
return (
<div>
<ul className="header">{navList}</ul>
</div>
)
}
}
ReactDOM.render(<Navigation />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
Related
I am trying to implement forward Ref in my Demo Project but I am facing one issue. The value of current coming from forward ref is null, but once I re-render my NavBar component (by sending a prop) I get the value of current.
I basically need to scroll down to my Section present in Home Component from NavBar Component.
It can be done by directly by giving a href attribute and passing the id. But I wanted to learn how forward ref works and hence this approach.
Can someone please help me with this?
Here is my Code.
import './App.css';
import NavBar from './components/NavBar/NavBar';
import Home from './components/Home/Home';
class App extends Component {
constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
}
render() {
return (
<div className="App">
<NavBar name={this.state.name} homeRef={{homeRefService: this.homeRefService , homeRefContact: this.homeRefContact}}/>
<Home ref={{homeRefService: this.homeRefService, homeRefContact: this.homeRefContact }}/>
</div>
);
}
}
export default App;
**Home Component**
import React from 'react';
const home = React.forwardRef((props , ref) => {
const { homeRefService , homeRefContact } = ref;
console.log(ref);
return (
<div>
<section ref={homeRefService} id="Services">
Our Services
</section>
<section ref={homeRefContact} id="Contact">
Contact Us
</section>
</div>
)
})
export default home
**NavBar Component**
import React, { Component } from 'react'
export class NavBar extends Component {
render() {
let homeRefs = this.props.homeRef;
let homeRefServiceId;
let homeRefContactId;
if(homeRefs.homeRefService.current) {
homeRefServiceId = homeRefs.homeRefService.current.id;
}
if(homeRefs.homeRefContact.current ) {
homeRefContactId = homeRefs.homeRefContact.current.id;
}
return (
<div>
<a href={'#' + homeRefServiceId}> Our Services</a>
<a href={'#' + homeRefContactId }>Contact Us</a>
</div>
)
}
}
export default NavBar
The ref is only accessible when the component got mounted to the DOM. So you might want to access the DOM element in componentDidMount.I suggest you to lift the state up to the parent component.
Demo
// App
class App extends React.Component {
constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
this.state = { homeServiceId: "", homeContactId: "" };
}
componentDidMount() {
this.setState({
homeServiceId: this.homeRefService.current.id,
homeContactId: this.homeRefContact.current.id
});
}
render() {
return (
<div className="App">
<NavBar
homeServiceId={this.state.homeServiceId}
homeContactId={this.state.homeContactId}
/>
<Home
ref={{
homeRefService: this.homeRefService,
homeRefContact: this.homeRefContact
}}
/>
</div>
);
}
}
// NavBar
export class NavBar extends Component {
render() {
return (
<div>
<a href={"#" + this.props.homeServiceId}> Our Services</a>
<a href={"#" + this.props.homeContactId}>Contact Us</a>
</div>
);
}
}
export default NavBar;
All your code just be oke. You can access ref after all rendered.
Example demo how do it work:
export class NavBar extends Component {
render() {
let homeRefs = this.props.homeRef;
console.log('from Nav Bar');
console.log(this.props.homeRef.homeRefService);
console.log('----');
let homeRefServiceId;
let homeRefContactId;
if(homeRefs.homeRefService.current) {
homeRefServiceId = homeRefs.homeRefService.current.id;
}
if(homeRefs.homeRefContact.current ) {
homeRefContactId = homeRefs.homeRefContact.current.id;
}
return (
<div>
<a href={'#' + homeRefServiceId}> Our Services</a>
<a href={'#' + homeRefContactId }>Contact Us</a>
</div>
)
}
}
const Home = React.forwardRef((props , ref) => {
const { homeRefService , homeRefContact } = ref;
useEffect(() => {
console.log('from home');
console.log(homeRefService);
console.log('----');
props.showUpdate();
})
return (
<div>
<section ref={homeRefService} id="Services">
Our Services
</section>
<section ref={homeRefContact} id="Contact">
Contact Us
</section>
</div>
)
})
class App extends Component {
state = {
name: 'init',
}
constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('from app');
console.log(this.homeRefService);
console.log('----');
}
render() {
return (
<div className="App">
<div>{this.state.name}</div>
<NavBar name={this.state.name} homeRef={{homeRefService: this.homeRefService , homeRefContact: this.homeRefContact}}/>
<Home showUpdate={() => this.state.name === 'init' && setTimeout(() => this.setState({name: 'UpdatedRef'}), 2000)} ref={{homeRefService: this.homeRefService, homeRefContact: this.homeRefContact }}/>
</div>
);
}
}
I have 4 divs that onClick call a function. When the particular div is clicked, I want the other divs to be non-clickable. But until the particular div is clicked, I want them to be clickable. My code:
import React, { Component } from 'react';
class MyApp extends Component {
state = {
div:2
}
handleClick = (id) => {
id==this.state.div?
//disable onClick for all divs :
//do nothing
}
render() {
return (
<div>
<div onClick={()=>this.handleClick(1)}>
1
</div>
<div onClick={()=>this.handleClick(2)}>
2
</div>
<div onClick={()=>this.handleClick(3)}>
3
</div>
<div onClick={()=>this.handleClick(4)}>
4
</div>
</div>
);
}
}
export default MyApp
How do I do this? Am I correct in disabling the click from the handleClick function?
Thanks.
This is another approach I made and I hope it makes sense and helps. let me know if you have any questions
class App extends Component {
constructor(props) {
super(props);
this.state = {
buttonClick: 2,
buttons: [1,2,3,4],
clicked: false
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(id){
console.log('clicked ', id)
this.setState({
buttonClick: id,
clicked: true
})
}
renderInitButtons() {
const {buttons} = this.state;
return buttons.map(button => {
return <div onClick={() => this.handleClick(button)}> {button} </div>
})
}
renderButtonClicked() {
const {buttons, buttonClick} = this.state;
return buttons.map(button => {
if(buttonClick === button) {
return <div onClick={() => this.handleClick(button)}> {button} </div>
}
return <div > {button} </div>
})
}
render() {
const {buttons, buttonClick, clicked} = this.state;
return (
<div>
{
clicked? this.renderButtonClicked(): this.renderInitButtons()
}
</div>
)
}
}
render(<App />, document.getElementById('root'));
While this is more of a semantic argument, you are still firing an event in each div, regardless of its state. You're just deciding whether or not any action should be taken. If you want to make it truly have no behavior, then you have to dynamically add/remove them. The easiest way is to iterate and create the 4 divs, with a conditional to see if an onClick listener should be added
buildDivs() {
return [1,2,3,4].map(id => {
const divProps = {}
if (this.state.div === id) {
divProps.onClick = () => this.handleClick(id)
}
return <div {...divProps} key={id}>{id}</div>
}
}
render() {
return (
<div>
{this.buildDivs}
</div>
)
}
You can add locked to your state and set it to true when you want to lock other divs and return from your function if its true
I would also change the handleClick function to return a new function to keep the code more readable
class MyApp extends Component {
state = {
div: 2,
locked: false
};
handleClick = id => () => {
if (this.state.locked) return;
if (id === this.state.div) {
this.setState({ locked: true });
}
};
render() {
return (
<div>
<div onClick={this.handleClick(1)}>1</div>
<div onClick={this.handleClick(2)}>2</div>
<div onClick={this.handleClick(3)}>3</div>
<div onClick={this.handleClick(4)}>4</div>
</div>
);
}
}
If you don't want to register any handler you can also check if this.state.locked is true and return null to the onClick function
class MyApp extends Component {
state = {
div: 2,
locked: false
};
handleClick = id => {
if (this.state.locked) return null;
return () => {
if (id === this.state.div) {
this.setState({ locked: true });
}
}
};
render() {
return (
<div>
<div onClick={this.handleClick(1)}>1</div>
<div onClick={this.handleClick(2)}>2</div>
<div onClick={this.handleClick(3)}>3</div>
<div onClick={this.handleClick(4)}>4</div>
</div>
);
}
}
I have a tab navigation at the top of my page, and I want to add an 'active' class to the tab when it's clicked and make sure it's not on any of the other tabs.
So far what I have adds the 'active' class to the first tab, but doesn't update if you click on any of the other tabs. So the first tab is always the active tab regardless of what you click on.
import React from 'react'
import { string } from 'prop-types'
class TabNav extends React.Component {
constructor(props) {
super(props)
this.state = {
currentTab: ''
}
this.handleClick = this.handleClick.bind(this)
this.createTabItems = this.createTabItems.bind(this)
}
shouldComponentUpdate (nextProps, nextState) {
return nextProps.navItems !== this.props.navItems
}
handleClick (currentTab) {
this.setState({
currentTab: currentTab
})
this.createTabItems()
}
createTabItems () {
const { navItems = false } = this.props
if (!navItems) return false
const splitItems = navItems.split(',')
if (!splitItems.length) return false
return splitItems.map((item, currentTab) => {
const items = item.split('_')
if (items.length !== 3) return null
const itemLink = items[1]
return (
<li key={currentTab} className={this.state.currentTab == currentTab ? 'side-nav-tab active' : 'side-nav-tab'} onClick={this.handleClick.bind(this, currentTab)}>
<a href={itemLink}>
<p>{ items[0] }</p>
</a>
</li>
)
})
}
render () {
const tabItems = this.createTabItems()
if (!tabItems) return null
return (
<div>
<ul id='tabNavigation'>
{tabItems}
</ul>
</div>
)
}
}
TabNav.propTypes = {
navItems: string.isRequired
}
export default TabNav
I have also tried calling this.createTabItems asynchronously in setState to try and force an update but that didn't work:
handleClick (currentTab) {
this.setState({
currentTab: currentTab
}, () => this.createTabItems)
}
I think your shouldComponentUpdate is causing the issue. Can you try removing it? Also, you don't need to call this.createTabItems in your handleClick()
I feel like a mindset shift is required here to think more declaratively, you don't need to tell React when you want to create the tabs, it will determine this itself from the render method and the data that's passed to it.
I've removed your componentShouldUpdate function because React will already do the comparison of those props for you. I've also tracked the selected item by index because it simplifies the logic in my mind.
Try something like this:
import React from 'react';
import { string } from 'prop-types';
class TabNav extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
handleClick = index => {
this.setState({
selectedIndex: index
});
};
render() {
const { navItems = [] } = this.props;
return (
<div>
<ul id="tabNavigation">
{navItems.map((navItem, index) => {
if (navItem.length !== 3) return;
const [text, link] = navItem;
return (
<li
className={
this.state.selectedIndex === index
? 'side-nav-tab active'
: 'side-nav-tab'
}
onClick={this.handleClick.bind(null, index)}
>
<a href={link}>
<p>{text}</p>
</a>
</li>
);
})}
}
</ul>
</div>
);
}
}
TabNav.propTypes = {
navItems: string.isRequired
};
export default TabNav;
There are a couple other changes in there, like destructuring from the array rather than using indexes so it's clearer what the properties you're pulling out of a navItem array are.
I also used a fat arrow function for the handleClick function because it means you don't have to bind to this in your constructor.
You got two problems, first the way you are using this in your function and secondly your shouldUpdateComponent, just like the other mentioned before me. If you want to reference your class using this you need to use arrow functions. remember that the ES6 arrow function uses lexical scoping which means that this references the code that contains the function. I made the changes in the code below with a working example.
class TabNav extends React.Component {
constructor(props) {
super(props)
this.state = {
currentTab: ''
}
this.handleClick = this.handleClick.bind(this)
this.createTabItems = this.createTabItems.bind(this)
}
handleClick (currentTab) {
this.setState({
currentTab: currentTab
})
}
createTabItems = () => {
const { navItems = false } = this.props
if (!navItems) return false
const splitItems = navItems.split(',')
if (!splitItems.length) return false
return splitItems.map((item, currentTab) => {
const items = item.split('_')
if (items.length !== 3) return null
const itemLink = items[1]
return (
<li key={currentTab} className={this.state.currentTab == currentTab ? 'side-nav-tab active' : 'side-nav-tab'} onClick={this.handleClick.bind(null, currentTab)}>
<a href={itemLink}>
<p>{ items[0] }</p>
</a>
</li>
)
})
}
render () {
const tabItems = this.createTabItems()
if (!tabItems) return null
return (
<div>
<ul id='tabNavigation'>
{tabItems}
</ul>
</div>
)
}
}
class Nav extends React.Component {
render() {
return(
<TabNav navItems={'Test1_#_Test1,Test2_#_Test2'} />)
}
}
ReactDOM.render(<Nav />, document.getElementById('root'))
.active {
font-size: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
.
I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.
I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.
Thank you!
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected }, () => {
this.sectionRef.current.changeSection(this.state.section)
})
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<Layout>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</Layout>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { this.props.selectSection("projects")}}>
{" "}
Projects
</span>
</header>
)
There seemed to be a couple of issues with your example code:
Missing closing div in Header
Using this.props instead of props in onclick in span in Header
The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected })
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<div>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</div>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { props.selectSection("projects")}}>
{" "}
Projects
</span>
</div>
</header>
)
ReactDOM.render(
<IndexPage />,
document.getElementsByTagName('body')[0]
);
.visible {
opacity: 1
}
.invisible {
opacity: 0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
There is a markup error in your code in Header component - div tag is not closed.
Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.
As #Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})
Any way here is a working example of what you trying to achieve:
// #flow
import * as React from "react";
import Header from "./Header";
type
Properties = {};
type
State = {
section: string,
headerVisible: boolean,
};
class IndexPage extends React.Component<Properties, State> {
static defaultProps = {};
state = {};
constructor(props) {
super(props);
this.state = {
section: "",
headerVisible: true,
};
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
setTimeout(
() => this.setState(
prevState => ({
section: sectionSelected,
headerVisible: !prevState.headerVisible
}),
() => console.log("Debug log: \n", this.state)
),
325
);
}
render(): React.Node {
const {section, headerVisible} = this.state;
return (
<React.Fragment>
<Header selectSection={this.displaySection} headerVisible={headerVisible} />
<br/>
<div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
</React.Fragment>
)
}
}
export default IndexPage;
and Header component
// #flow
import * as React from "react";
type Properties = {
headerVisible: boolean,
selectSection: (section: string) => void
};
const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
const headerRef = React.useRef(null);
return (
<React.Fragment>
<header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => selectSection("projects")}>Projects</span>
</div>
</header>
<br/>
<div>Header class name: {headerRef.current && headerRef.current.className}</div>
</React.Fragment>
);
};
export default ComponentName;
I am have a sidebar that is fixed with icons to navigate to different pages. when the icons are clicked, a secondary menu slides out. Currently when an icon is clicked, the menu slides out, but when another icon is selected, the menu slides back in rather than just switching or staying out. Also clicking out of the sidebar and menu does not close the sliding menu. I am not sure how to get this to work. Any suggestions would be appreciated.
Parent:
export default class Parent extends Component {
constructor() {
super();
this.state = {
menuOpen: false,
};
}
toggleMenuOpen = () => {
this.setState({ menuOpen: !this.state.menuOpen });
}
render() {
return (
<div>
<ul>
<NavIcons onClick={this.toggleMenuOpen} />
<PushMenu menuOpen={this.state.menuOpen} />
</ul>
</div>
);
}
}
Icons on sidebar:
export default class NavIcons extends Component {
render() {
const { onClick } = this.props;
return (
<div>
{
topNavListItems.map((topItem) => {
return (
<li className='link-wrapper' key={topItem.get('id')} onClick={onClick}>
<NavLink
activeClassName='selected'
className='Icon-list-link'
to={topItem.get('route')}
>
<Icon name={topItem.get('name')} />
</NavLink>
</li>
);
})
}
</div>
);
}
}
NavIcons.propTypes = {
onClick: PropTypes.func,
};
Slide out menu:
export default class PushMenu extends Component {
render() {
const { menuOpen } = this.props;
const menuClass = menuOpen ? 'menu open' : 'menu';
return (
<div className={menuClass}>
<Header>Content</Header>
<Linebreak />
<List>
{
labelMenuItems.map((menuItem) => {
return (
<li key={menuItem.get('id')}>{menuItem.get('name')}
<List>
{
menuItem.get('nestedItems').map((childMenuItem) => {
return (
<li key={childMenuItem.get('id')}>{childMenuItem.get('name')}</li>
);
})
}
</List>
</li>
);
})
}
</List>
</div>
);
}
}
PushMenu.propTypes = {
menuOpen: PropTypes.bool,
};
Rather than have a single toggleMenuOpen handler, create openMenu and closeMenu handlers
openMenu = () => {
this.setState(prevState => {
if (!prevState.menuOpen) {
return { menuOpen: true };
}
return {};
})
}
closeMenu = () => {
this.setState(prevState => {
if (prevState.menuOpen) {
return { menuOpen: false };
}
return {};
})
}
You can then use openMenu as the onClick handler for your top-level nav li items
If you have access to the main content in the Parent component, you can also handle the close-on-click-outside (if not, you would need to hoist the menuOpen state up to where you can). Use closeMenu as a click handler on the "outside" content - whatever's not in the slideout menu. You would want to make sure to only set that handler when the menu is open. There are many ways to do this but this is one:
render() {
const handleCloseWhenOpen = this.state.menuOpen ? { onClick: this.closeMenu } : {};
return (
<div>
<ul>
<NavIcons onClick={this.toggleMenuOpen} />
<PushMenu menuOpen={this.state.menuOpen} />
</ul>
<MainContent {...handleCloseWhenOpen} />
</div>
);
}