Handle click outside of return - reactjs

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)} />
);
}
}

Related

REACT: Can´t pass parameter to a lifting up event handler

I can´t pass the parameters from a child component to a parent component using the same event handler.
My components' tree follows this structure:
ProductList
ProductsGrid
PaginationBar
In the ProducList component I have this event handler
handlePagination = (index) => { bla bla bla }
<PaginationBar
OnPagination={(index) => this.handlePagination(index)}
pages={this.state.pages}
/>
In the Pagination Bar when I render the page.
render() {
return (
<nav aria-label="Catalog page navigation">
<ul className="pagination">
<li className={this.navButtonEnabled(0)} key="page-item-0">
<a
className="page-link"
onClick={this.props.OnPagination(0)}
href="#"
aria-label="Previous"
>
<span aria-hidden="true">Previous 3</span>
</a>
</li>
<li className={this.navButtonEnabled(1)} key="page-item-1">
<a
className="page-link"
onClick={this.props.OnPagination(1)}
href="#"
>
<span aria-hidden="true">
{this.navButtonDisplay(this.props.pages.navigation[1])}
</span>
</a>
</li>
<li className={this.navButtonEnabled(2)} key="page-item-2">
<a
className="page-link"
onClick={this.props.OnPagination(2)}
href="#"
>
<span aria-hidden="true">
{this.navButtonDisplay(this.props.pages.navigation[2])}
</span>
</a>
</li>
<li className={this.navButtonEnabled(3)} key="page-item-3">
<a
className="page-link"
onClick={this.props.OnPagination(3)}
href="#"
>
<span aria-hidden="true">
{this.navButtonDisplay(this.props.pages.navigation[3])}
</span>
</a>
</li>
<li className={this.navButtonEnabled(4)} key="page-item-4">
<a
className="page-link"
onClick={this.props.OnPagination(4)}
href="#"
aria-label="Next"
>
<span aria-hidden="true">Next 3</span>
</a>
</li>
</ul>
</nav>
);
}
}
In the onClick event, I call the event handler with the index number (this is the parameter I want to pass), according the button clicked I render correctly the ProductsGrid. But it doesn't work at all, I tried several approaches, I just want to pass a number via a parameter for a lifting up event handler. What am I doing wrong?
This is the error presented in the console.
react_devtools_backend.js:6 Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
in Pagination (created by Catalog)
in Catalog (created by App)
in main (created by App)
in App
I am not changing any state during the render process, the event handler changes the status, when the button is clicked. This is not a render process.
Please replace
onClick={this.props.OnPagination(0)}
With
onClick={() => {this.props.OnPagination(0)}}
Here is complete code (I have commented some code so that I can run this easily to my end):
import React from "react";
export default class ProductList extends React.Component {
handlePagination = (index) => {
console.log(index, 'index');
};
render() {
return (
<PaginationBar OnPagination={(index) => this.handlePagination(index)}
// pages={this.state.pages}
/>
);
}
}
class PaginationBar extends React.Component {
render() {
return (
<nav aria-label="Catalog page navigation">
<ul className="pagination">
<li key="page-item-0">
<a
className="page-link"
onClick={() => {this.props.OnPagination(0)}}
href="#"
aria-label="Previous"
>
<span aria-hidden="true">Previous 3</span>
</a>
</li>
<li key="page-item-1">
<a
className="page-link"
onClick={() => {this.props.OnPagination(1)}}
href="#"
>
<span aria-hidden="true">
{/*{this.navButtonDisplay(this.props.pages.navigation[1])}*/}
Previous 2
</span>
</a>
</li>
<li key="page-item-2">
<a
className="page-link"
onClick={() => {this.props.OnPagination(2)}}
href="#"
>
<span aria-hidden="true">
{/*{this.navButtonDisplay(this.props.pages.navigation[2])}*/}
Previous 1
</span>
</a>
</li>
<li key="page-item-3">
<a
className="page-link"
onClick={() => {this.props.OnPagination(3)}}
href="#"
>
<span aria-hidden="true">
{/*{this.navButtonDisplay(this.props.pages.navigation[3])}*/}
Next 2
</span>
</a>
</li>
<li key="page-item-4">
<a
className="page-link"
onClick={() => {this.props.OnPagination(4)}}
href="#"
aria-label="Next"
>
<span aria-hidden="true">Next 3</span>
</a>
</li>
</ul>
</nav>
);
}
}

React Redux and React Router connect

when I try to dispatch an event on logout function it shows me an error I think the way I am connecting Redux is wrong but I'm not sure can anyone solve this?
Logout Function
Auth.logout(() => {
Firebase.auth().signOut().then(() => {
this.props.updateState();
});
})
Dispatch
const mapDispatchToProps = dispatch => {
return{
updateState: () => dispatch({type: 'UPDATE_REDUX_STATE'})
}
}
Export with connect and withRouter
export default withRouter(connect(null, mapDispatchToProps)(Navigation))
Navigation Component
class Navigation extends Component {
logoutHander(){
Auth.logout(() => {
Firebase.auth().signOut().then(() => {
this.props.updateState();
});
})
}
render(){
return(
<div className="custom-container-fluid">
<nav className="navbar navbar-expand-md text-center">
<div className="container">
<a className="navbar-brand" href="/">FG</a>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
{
Auth.isAuthenticated() === true ?
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="nav-link" to="/home"><i className="fas fa-home" style={{marginRight: '3px'}}></i>Home</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/myrecipe"><i className="fas fa-stream" style={{marginRight: '3px'}}></i>My Recipes</Link>
</li>
<li className="nav-item" onClick={this.logoutHander}>
<Link className="nav-link" to="/"><i className="fas fa-user" style={{marginRight: '3px'}}></i>Logout</Link>
</li>
<li className="nav-item upload-nav">
<Link className="nav-link" to="/uploadRecipe"><i className="fas fa-upload" style={{marginRight: '3px'}}></i>Upload Recipe</Link>
</li>
</ul>
:
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="nav-link" to="/login"><button className="btn nav-btn-signup"><i className="fas fa-user" style={{marginRight: '3px'}}></i>Login</button></Link>
</li>
</ul>
}
</div>
</div>
</nav>
</div>
);
}
Error I am getting is Cannot read property 'props' of undefined
This reference not possessed inside the firebase method,
can you pass this instance value to some other variable, Current instance will be copied when the function till complete
constructor(props) {
super(props);
}
logoutHander(){
const self = this;
Auth.logout(() => {
Firebase.auth().signOut().then(() => {
self.props.updateState();
});
})
}

React Issue With Conditional Then Map

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!

Show and hide sub menu using React Hooks

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.

How can I use this dropdown logic in more than one <li> element?

I created a simple dropdown button using react, it's working fine but now I want to use the same logic to use it on Two more buttons.
How can I take the same logic that I'm using in 1st Dropdown menu and use in the other two elements 2nd Dropdown menu and 3rd Dropdown menu ?
Here is a link that shows the code in action : http://codepen.io/rkhayat/pen/e3581b3625fc6b6f8fa5a8dab2a28a41
Here's my code:
class Header extends Component {
constructor(props){
super(props)
this.state = {
open:false
}
}
_handleDropDown(){
this.setState({
open: !this.state.open
})
}
render() {
return (
<nav className="navbar">
<ul>
<li>
<div onClick={this._handleDropDown.bind(this)} className="dropdown open">
<button className="btn btn-link dropdown-toggle "type="button" > 1st Dropdown menu</button>
{
this.state.open
?
<ul className="dropdown-menu">
<li>Adventure Tours</li>
<li><a href="#">Airport transfers</a</li>
<li>Car rental</li>
<li><a href="#">Sightseeing tours</a</li>
</ul>
:
null
}
</div>
</li>
<li>2nd Dropdown menu</li>
<li>3rd Dropdown menu</li>
</ul>
</nav>
)
}
}
Update:
I came up with a solution that work but I don't know if it's a best practice:
Here is the code:
class Header extends Component {
constructor(props){
super(props)
this.state = {
open:false,
open2:false
}
}
_handle1stDropDown(){
this.setState({
open: !this.state.open
})
}
_handle2ndDropDown(){
this.setState({
open2: !this.state.open2
})
}
render() {
return (
<nav className="navbar">
<ul>
<li>
<div onClick={this._handle1stDropDown.bind(this)} className="dropdown open">
<button className="btn btn-link dropdown-toggle "type="button" > 1st Dropdown menu</button>
{
this.state.open
?
<ul className="dropdown-menu">
<li>1st dropdown 1st li</li>
<li><a href="#">1st dropdown 2nd li</a</li>
<li>1st dropdown 3rd</li>
<li><a href="#">1st dropdown 4th</a</li>
</ul>
:
null
}
</div>
</li>
<li>
<div onClick={this._handle2ndDropDown.bind(this)} className="dropdown open">
<button className="btn btn-link dropdown-toggle "type="button" > 2nd Dropdown menu</button>
{
this.state.open
?
<ul className="dropdown-menu">
<li>2nd dropdown 1st li</li>
<li><a href="#">2nd dropdown 2nd li</a</li>
<li>2nd dropdown 3rd li</li>
<li><a href="#">2nd dropdown 4th li</a</li>
</ul>
:
null
}
</div>
</li>
</ul>
</nav>
)
}
}
What you would need to do is extract the button and the dropdown into its own component. In order for that to work you would need to make them compossible components meaning that you would pass in the items in the dropdown as props. I made a codepen showing what I mean
http://codepen.io/finalfreq/pen/ozBxrZ
class Dropdown extends React.Component {
constructor(props) {
super()
}
createItems() {
return this.props.items.map(function(item, index) {
return <li> {item.title} </li>
})
}
render() {
var items = this.createItems();
return (
<div onClick={this.props.onClick} className="dropdown open">
<button className="btn btn-default dropdown-toggle" type="button">
Dropdown
<span className="caret" />
</button>
{
this.props.open
?
<ul className="dropdown-menu">
{items}
</ul>
:
null
}
</div>
)
}
}
you'd have to customize it to your needs but you can put dropdown anywhere just need to give it the right props

Resources