I'm trying to redo my menu with React Hooks and here's my latest attempt.
The only issue I have is that when I click sub-menu-click, all the sub menu opens.
How can I make it so that only the child is open when the parent (i.e. Item 1) is clicked?
Thank you.
import React, { useState } from 'react';
export default () => {
const [menuOpen, setMenuOpen] = useState(false);
const [subMenuOpen, setSubMenuOpen] = useState(false);
const toggleClassName = menuOpen ? 'is-open' : '';
const data = useStaticQuery(graphql`
query {
site {
siteMetadata {
title
}
}
}
`);
return (
<Layout>
<Menu>
<div class="menu-left">
<Link to="/">{data.site.siteMetadata.title}</Link>
</div>
<Hamburger
className={toggleClassName}
onClick={() => setMenuOpen(!menuOpen)}
>
<span></span>
<span></span>
<span></span>
</Hamburger>
<div class="menu-right">
<ul className={toggleClassName}>
<li class="menu-item has-children">
<Link to="/">
Item 1
</Link>
<div
class={`sub-menu-click`}
onClick={() => setSubMenuOpen(!subMenuOpen)}
>
<span></span>
</div>
<ul
class={`sub-menu && ${
subMenuOpen ? 'is-open' : ''
}`}
>
<li class="menu-item">Sub-Item 1</li>
<li class="menu-item">Sub-Item 2</li>
<li class="menu-item">Sub-Item 3</li>
</ul>
</li>
<li class="menu-item has-children">
<Link to="/">
Item 2
</Link>
<div
class={`sub-menu-click`}
onClick={() => setSubMenuOpen(!subMenuOpen)}
>
<span
class={`${subMenuOpen ? 'is-open' : ''}`}
></span>
</div>
<ul
class={`sub-menu && ${
subMenuOpen ? 'is-open' : ''
}`}
>
<li class="menu-item">Sub-Item 1</li>
<li class="menu-item">Sub-Item 2</li>
<li class="menu-item">Sub-Item 3</li>
</ul>
</li>
</ul>
</div>
</Menu>
</Layout>
);
};
The simplest solution to this to move the SubMenu to a different component and let it have it's own state.
const SubMenu = () => {
const [subMenuOpen, setSubMenuOpen] = useState(false);
return (
<li class="menu-item has-children">
<Link to="/">
Item 1
</Link>
<div
class={`sub-menu-click`}
onClick={() => setSubMenuOpen(!subMenuOpen)}
>
<span></span>
</div>
<ul
class={`sub-menu && ${
subMenuOpen ? 'is-open' : ''
}`}
>
<li class="menu-item">Sub-Item 1</li>
<li class="menu-item">Sub-Item 2</li>
<li class="menu-item">Sub-Item 3</li>
</ul>
</li>
)
}
and render this component in place for SubMenu in Menu component.
In case you need to control the opening of SubMenu from the Menu component, you have an array of subMenuIds currently open or a single ID according to your use case.
Related
This is not something new but for me doing toggle among the side menu item is bit challenging in React application. Please have a look in code and suggest the possible solution:
Here I have multiple menu item and if I click on perticular menu item it should show selected/active:
<div className="offcanvas offcanvas-start sidebar-nav theme-custom-gray"
tabindex="-1"
id="sidebar">
<ul>
<li>
<i>
Dashboard
</li>
<li><hr /></li>
<li>
product
</li>
<li>
<a href="#layouts">
<i>
Create New
<i>
</a>
<ul>
<li>
MenuItem
</li>
<li>
product 1
</li>
<li>
product 2
</li>
<li>
product 3
</li>
</ul>
</li>
<li>
<i>
View List
</li>
<li><hr /></li>
</ul>
App.jsx:
import { useState } from "react";
import "./styles.css";
const menuItems = ["MenuItem 1", "MenuItem 2", "MenuItem 3", "MenuItem 4"];
const App = () => {
const [selectedMI, setSelectedMI] = useState(menuItems[0]);
return (
<ul>
{menuItems.map((menuItem) => (
<li
className={selectedMI === menuItem ? "selected" : ""}
onClick={() => setSelectedMI(menuItem)}
>
{menuItem}
</li>
))}
</ul>
);
};
export default App;
styles.css:
li {
cursor: pointer;
}
li.selected {
text-decoration: underline;
}
One of my current projects is learning React and coding in general by analyzing and playing with specific code fragments.
But now I'm stuck. I think I know what the problem is, but I can't solve it.
I have 2 tabs. Tab 1 is set as active, so it's content is shown if i vist the site. Tab 2 is set as inactive. If I click on Tab 2, the state is simply switched. This works.
But if I add a third tab as in this example, the behaviour get's weird.
If I click on Tab 2, Tab 3 is set as active as well and both contents are shown.
I would really appreciate some help. I don't want to give up understanding this.
Thanks!
const [main__tab_state, set_main__tab_state] = useState(true);
<div className="main__tab">
<div className="page__flex">
<div className="main__tab--box">
<div className="main__tab--head">
<ul>
<li className={main__tab_state ? "active" : ""}>
<a className="js-tab" data-tab="tab" onClick={() => set_main__tab_state(true)}>TAB NAME 1</a>
</li>
<li className={main__tab_state ? "" : "active"}>
<a className="js-tab" data-tab="tab1" onClick={() => set_main__tab_state(false)}>TAB NAME 2</a>
</li>
<li className={main__tab_state ? "" : "active"}>
<a className="js-tab" data-tab="tab2" onClick={() => set_main__tab_state(false)}>TAB NAME 3</a>
</li>
</ul>
</div>
</div>
<div className="main__tab--box">
<div className="main__tab--body js-tab__body">
<div id="tab" className={`main__tab--item${main__tab_state ? "active" : ""}`}>
<div className="text">
<p>
TAB TEXT 1
</p>
</div>
</div>
<div id="tab1" className={`main__tab--item${main__tab_state ? "" : "active"}`}>
<div className="text">
<p>
TAB TEXT 2
</p>
</div>
</div>
<div id="tab2" className={`main__tab--item${main__tab_state ? "" : "active"}`}>
<div className="text">
<p>
TAB TEXT 3
</p>
</div>
</div>
</div>
</div>
</div>
</div>
Use const [activeTabIndex, setActiveTabIndex] = useState(0); instead.
Then just kodify your code like so:
<a className="js-tab" data-tab="tab" onClick={() => setActiveTabIndex(0)}>TAB NAME 1</a>
<div id="tab" className={`main__tab--item${activeTabIndex === 0 ? "active" : ""}`}>
First of all you need to change your state.
const [main__tab_state, set_main__tab_state] = useState("tab1"); // change state to store string values and default we will store tab1
Change your code to render tabs and content like below:-
<div className="main__tab">
<div className="page__flex">
<div className="main__tab--box">
<div className="main__tab--head">
<ul>
<li className={main__tab_state === "tab1" ? "active" : ""}>
<a className="js-tab" data-tab="tab" onClick={() => set_main__tab_state("tab1")}>TAB NAME 1</a>
</li>
<li className={main__tab_state === "tab2" ? "" : "active"}>
<a className="js-tab" data-tab="tab1" onClick={() => set_main__tab_state("tab2")}>TAB NAME 2</a>
</li>
<li className={main__tab_state === "tab3" ? "" : "active"}>
<a className="js-tab" data-tab="tab2" onClick={() => set_main__tab_state("tab3")}>TAB NAME 3</a>
</li>
</ul>
</div>
</div>
<div className="main__tab--box">
<div className="main__tab--body js-tab__body">
<div id="tab" className={`main__tab--item${main__tab_state === "tab1" ? "active" : ""}`}>
<div className="text">
<p>
TAB TEXT 1
</p>
</div>
</div>
<div id="tab1" className={`main__tab--item${main__tab_state === "tab2" ? "" : "active"}`}>
<div className="text">
<p>
TAB TEXT 2
</p>
</div>
</div>
<div id="tab2" className={`main__tab--item${main__tab_state === "tab3" ? "" : "active"}`}>
<div className="text">
<p>
TAB TEXT 3
</p>
</div>
</div>
</div>
</div>
</div>
</div>
I have a simple footer component. It is used in about and support pages. In my sass, I have set the position to relative.
I want to change this position based on the route. If it's /about then position: relative and it's /support then position: fixed.
Is it possible to achieve this?
function GeneralFooter() {
return (
<div class="container">
<div class="row">
<div class="col-lg-4 col-md-4 col-sm-6 col-xs-6">
<div class="footer-pad">
<ul class="list-unstyled">
<li>
<a
className="nav-link"
href="*"
target="_blank"
rel="noopener noreferrer"
>
Help
</a>
</li>
</ul>
</div>
</div>
<div class="col-lg-4 col-md-4 col-sm-6 col-xs-6">
<div class="footer-pad">
<ul class="list-unstyled">
<li className="nav-item">
<NavLink to="/about" className="nav-link">
<span>About</span>
</NavLink>
</li>
</ul>
</div>
</div>
<div class="col-lg-4 col-md-4 col-sm-6 col-xs-6">
<div class="footer-pad">
<ul class="list-unstyled">
<li className="nav-item">
<NavLink to="/support" className="nav-link">
<span>Support</span>
</NavLink>
</li>
</ul>
</div>
</div>
</div>
</div>
);
}
I'm not sure if you are using any libraries, but without, you can use the following code.
Using the style prop:
function GeneralFooter() {
const location = useLocation();
const pathName = location.pathname;
return (
<div
className="container"
style={{
position: pathName === '/about' ? 'relative' : pathName === '/support' ? 'fixed' : 'inherit'
}}
>
...
Using the className prop
.footer--about {
position: relative;
}
.footer--support {
position: fixed;
}
function GeneralFooter() {
const location = useLocation();
const pathName = location.pathname;
const extraClassName = pathName === '/about' ? 'footer--about' : pathName === '/support' ? 'footer--support' : '';
return (
<div
className={`container ${extraClassName}`}
>
...
With classNames dependency:
function GeneralFooter() {
const location = useLocation();
const rootClassName = classNames('container', {
'footer-about': location.pathname === '/about',
'footer-support': location.pathname === '/support',
});
return (
<div className={rootClassName}>
...
I have some react code that checks if an array has any elements. If it does, then it should map over the elements and display them. I am able to do the check but the map throws an error, and I do not know why.
Here is the function:
function MetalOptions() {
const metals_options = GetsOptions(product,"Metal");
if (metals_options.length > 0) {
return(
<React.Fragment>
<h1>yes</h1>
<ul>
{metals_options.map((option,index) =>
<li key={index}>{option}</li>
)}
</ul>
</React.Fragment>
);
}
}
Here is the entire render function:
render() {
const { isLoading, productID, product} = this.state;
function GetsOptions(product,kind) {
const variant_metals = [];
product.variants.map((variant) =>
variant.option_values.map((option) =>
variant_metals.push({type: option.option_type_name,value: option.presentation})
)
);
const filter_variant_metals = variant_metals.filter(item => item.type === kind);
const output = [...new Set(filter_variant_metals.map(s => JSON.stringify(s)))].map(s => JSON.parse(s));
return output;
}
function GetsVariantImages(product) {
const variant_images = [];
product.variants.map((variant) =>
variant.images.map((image) =>
variant_images.push({
original: image.product_url,
thumbnail: image.small_url,
description: image.alt,
originalAlt: image.alt,
thumbnailAlt: image.alt
})
)
);
const output4 = [...new Set(variant_images.map(s => JSON.stringify(s)))].map(s => JSON.parse(s));
return output4;
}
if (isLoading) {
return <div className="sweet-spinner">
<BounceLoader
sizeUnit={"px"}
size={30}
color={"#494847"}
loading={isLoading}
/>
</div>
}
function MetalOptions() {
const metals_options = GetsOptions(product,"Metal");
const metals_options_array = Object.values(metals_options);
if (metals_options.length > 0) {
return(
<React.Fragment>
<h1>yes</h1>
<ul>
{metals_options_array.map((option,index) =>
<li key={index}>{option}</li>
)}
</ul>
</React.Fragment>
);
}
}
const size_options = GetsOptions(product,"Center Diamond Size");
const shape_options = GetsOptions(product,"Center Diamond Shape");
return (
<div>
<div className="container">
<div className="row">
<div className="col-md-7">
{product.master.images.length >= 3?
<section className="main-image-grid">
{product.master.images.slice(0,3).map(image =>
<img key={image.id} src={image.large_url} alt={image.alt}/>
)}
</section>:
<section className="main-image-grid">
{product.master.images.slice(0,1).map(image =>
<img key={image.id} src={image.large_url} alt={image.alt}/>
)}
</section>
}
</div>
<div className="col-md-5 gradient-silver">
<h1 className="text-center">{product.name}</h1>
<p className="text-center">Your Price {product.display_price}</p>
<div className="cta">
<a href={"sms:+19137258268&body=Hello!%20I%20want%20more%20information%20on%20"+product.name}>Text For More Info!</a>
Call To Take A Look
</div>
<h2>Select Your Options</h2>
<MetalOptions />
<p>This comes in various options listed below:</p>
{GetsOptions(product,"Metal").length > 0 &&
<h4>yes</h4>
}
{GetsOptions(product,"Center Diamond Shape").length > 0 &&
<h2>
You have Center Diamond Shape {GetsOptions(product,"Center Diamond Shape").length} Options.
<ul>
</ul>
</h2>
}
{GetsOptions(product,"Center Diamond Size").length > 0 &&
<h2>
You have Center Diamond Size {GetsOptions(product,"Center Diamond Size").length} Options.
</h2>
}
</div>
</div>
<hr className="style-two"/>
<ul className="nav justify-content-center" id="productTab" role="tablist">
<li className="nav-item">
<a className="nav-link active" id="center-tab" data-toggle="tab" href="#center" role="tab" aria-controls="center" aria-selected="false">Center Gemstones</a>
</li>
<li className="nav-item">
<a className="nav-link" id="description-tab" data-toggle="tab" href="#description" role="tab" aria-controls="description" aria-selected="true">Description</a>
</li>
<li className="nav-item">
<a className={"nav-link " + (product.product_properties.length < 0 && 'disabled')} id="properties-tab" data-toggle="tab" href="#properties" role="tab" aria-controls="properties" aria-selected="false">Properties</a>
</li>
</ul>
<div className="tab-content" id="myTabContent">
<div className="tab-pane fade show active" id="center" role="tabpanel" aria-labelledby="center-tab">
<h3 className="h5 text-uppercase text-center">The Center</h3>
<p class="h6 text-center text-muted"><small>Deciding on a center diamond or gemstone is the <span className="text-uppercase font-weight-bold">important decision.</span> The center diamond holds the sentiment and value. If your piece of jewelry is a play, the mounting is a stage, and the center is the actors and script.</small></p>
<p class="h6 text-center text-muted"><small>We have helped so many in Kansas City find beautiful diamonds or gemstones in all qualities and price ranges usually complete with a GIA grading report. We would be honored to help you to your perfect one.</small></p>
<DiamondLinks />
<GemLinks />
</div>
<div className="tab-pane fade" id="description" role="tabpanel" aria-labelledby="description-tab"><p className="text-center">{product.description}</p></div>
<div className="tab-pane fade" id="properties" role="tabpanel" aria-labelledby="properties-tab">
{product.product_properties.map((property,index) =>
<p><span className="font-weight-bold">{property.property_name}:</span> {property.value}</p>
)}
</div>
</div>
</div>
<hr className="style-two"/>
<h2>More Pictures</h2>
<ImageGallery items={GetsVariantImages(product)} lazyLoad={true} showThumbnails={true} thumbnailPosition={'left'}/>
</div>
);
}
}
Any ideas? Thank you for your help!
I have a click that calls the setmenu function (in the className="menu" list) but I dont want to repeat it in my 3 different returns. Instead I want to put it into its own function/component and call it 3 times.
However when I move it the click cant find setmenu anymore, even though I still have this.setmenu = this.setmenu.bind(this); in the constructor?
setmenu(event, value){
this.setState({showForm: value});
console.log(this.state.showForm, "this.state.showForm")
}
render() {
const showForm = this.state.showForm;
if (showForm === 1){
return (
<div>
<ul className="menu">
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 1)}>Form 1</a></li>
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 2)}>Form 2</a></li>
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 3)}>Form 3</a></li>
</ul>
<FacebookLoginForm value={this.state.facebookResponse} />
</div>
);
}
else if(showForm === 2) {
return (
<div>
<ul className="menu">
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 1)}>Form 1</a></li>
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 2)}>Form 2</a></li>
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 3)}>Form 3</a></li>
</ul>
<ManualLoginForm />
</div>
);
}
else if (showForm === 3){
return (
<div>
<ul className="menu">
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 1)}>Form 1</a></li>
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 2)}>Form 2</a></li>
<li><a href="javascript:void(0)" onClick={e => this.setmenu(e, 3)}>Form 3</a></li>
</ul>
<ManualRegForm />
</div>
)
}
}
Some variations about #wostex answer
lets read additional parameters from href attribute instead of calling handler with additional arguments
event should be prevent by event API instead of old days construction href="javascript:void(0)"
Links container can support as many as needed links inside itself
function Links(props) {
return (
<div>
<ul className="menu">
{props.children.map((e, key) => <li key={key} onClick={props.clickHandler}>{e}</li>)}
</ul>
</div>
);
}
class App extends React.Component {
constructor() {
super()
this.setmenu = this.setmenu.bind(this);
}
setmenu(event, value){
event.preventDefault();
console.log('setmenu: ', event.target.getAttribute('href'))
}
render() {
return (
<Links clickHandler={this.setmenu}>
Form 3
Form 2
</Links>
);
}
}
ReactDOM.render(<App/>,
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">Loading...</div>
Working example: https://jsfiddle.net/wostex/4wcdL6dw/3/
Just pass your function as a prop.
function ThreeLinks(props) {
return (
<div>
<ul className="menu">
<li><a href="javascript:void(0)" onClick={(e) => props.clickHandler(e, 1)}>Form 1</a></li>
<li><a href="javascript:void(0)" onClick={(e) => props.clickHandler(e, 2)}>Form 2</a></li>
<li><a href="javascript:void(0)" onClick={(e) => props.clickHandler(e, 3)}>Form 3</a></li>
</ul>
</div>
);
}
class App extends React.Component {
setmenu(event, value){
console.log('setmenu: ', event, value)
}
render() {
return (
<ThreeLinks clickHandler={this.setmenu.bind(this)} />
);
}
}