React - ul with onBlur event is preventing onClick from firing on li - javascript

I've created a mobile dropdown menu that toggles open and closed based on state. Once it's open, I would like the user to be able to close the dropdown by clicking anywhere outside the ul.
I'm setting the tabIndex attribute on the ul to 0, which gives the ul "focus". I've also added an onBlur event to the ul that triggers the state change (dropdownExpanded = false) that hides the ul.
<ul tabIndex="0" onBlur={this.hideDropdownMenu}>
<li onClick={this.handlePageNavigation}>Page 1</li>
<li onClick={this.handlePageNavigation}>Page 2</li>
<li onClick={this.handlePageNavigation}>Page 3</li>
</ul>
However, when I implement this fix, the onClick events that I have on each li element fail to fire.
I know something is going on with the event bubbling, but I am at a lose as to how to fix it. Can anyone help?
NOTE:
I know you can create a transparent div below the ul that spans the entire viewport and then just add an onClick even to that div that will change the state, but I read about this tabIndex/focus solution on Stack Overflow and I'd really like to get it working.
Here is a more complete view of the code (the dropdown is for users to select their home country, which updates the ui):
const mapStateToProps = (state) => {
return {
lang: state.lang
}
}
const mapDispatchToProps = (dispatch) => {
return { actions: bindActionCreators({ changeLang }, dispatch) };
}
class Header extends Component {
constructor() {
super();
this.state = {
langListExpanded: false
}
this.handleLangChange = this.handleLangChange.bind(this);
this.toggleLangMenu = this.toggleLangMenu.bind(this);
this.hideLangMenu = this.hideLangMenu.bind(this);
}
toggleLangMenu (){
this.setState({
langListExpanded: !this.state.langListExpanded
});
}
hideLangMenu (){
this.setState({
langListExpanded: false
});
}
handleLangChange(e) {
let newLang = e.target.attributes['0'].value;
let urlSegment = window.location.pathname.substr(7);
// blast it to shared state
this.props.actions.changeLang( newLang );
// update browser route to change locale, but stay where they are at
browserHistory.push(`/${ newLang }/${ urlSegment }`);
//close dropdown menu
this.hideLangMenu();
}
compileAvailableLocales() {
let locales = availableLangs;
let selectedLang = this.props.lang;
let markup = _.map(locales, (loc) => {
let readableName = language[ selectedLang ].navigation.locales[ loc ];
return (
<li
key={ loc }
value={ loc }
onMouseDown={ this.handleLangChange }>
{ readableName }
</li>
);
});
return markup;
}
render() {
let localeMarkup = this.compileAvailableLocales();
return (
<section className="header row expanded">
< Navigation />
<section className="locale_selection">
<button
className="btn-locale"
onClick={this.toggleLangMenu}>
{this.props.lang}
</button>
<ul
className={this.state.langListExpanded ? "mobile_open" : " "}
value={ this.props.lang }
tabIndex="0"
onBlur={this.hideLangMenu}>
>
{ localeMarkup }
</ul>
</section>
</section>
)
}
}

Try using onMouseDown instead of onClick.

The point is the onBlur is triggering a re-render which seems to lead the browser to do not follow up with the onClick: https://github.com/facebook/react/issues/4210
But if you check the onBlur event you can find some info about what's happening, event.relatedTarget is populated and you can use these info to detect when the onBlur is actually triggered by the onClick and chain whatever you need to do.

I just ran into this with an array of breadcrumb links, where an onBlur handler was causing a rerender, preventing the link click from working. The actual problem was that react was regenerating the link elements every time, so when it rerendered, it swapped the link out from under the mouse, which caused the browser to ignore the click.
The fix was to add key properties to my links, so that react would reuse the same DOM elements.
<ol>
{props.breadcrumbs.map(crumb => (
<li key={crumb.url}>
<Link to={crumb.url} >
{crumb.label}
</Link>
</li>
))}
</ol>

Related

disabled={true} HTML button attribute does not work right in JavaScript + React

Having issues with disabled={true} HTML button. App is in JavaScript + React. I set button initial value disabled={true} .For example purposes I will call it id="button1". Once I click another button let's say id=button2, I re-enable my button1 with code document.getElementById('button1').disabled=false;. Button re-enables but once clicked it has no functionality, it looks like it is still disabled. What am I doing wrong ?
OK here is my simplified code:
function App() {
const approve = () => {
console.log('test');
};
const filterWeek2 = () => {
document.getElementById('approve').removeAttribute("disabled");
};
return (
<div className="App">
<nav>
<ul className="nav-area">
<li><button id="Week2" onClick={filterWeek2}>WEEK 2</button></li>
<li><button id="approve" onClick={approve} disabled="disabled">APPROVE</button></li>
</ul>
</nav>
</div>
);
}
export default withAuthenticator(App);
The disabled is a boolean attribute, Its presence on an HTML elements disables the element. If it is present on the element, it will disable the HTML element, doesn't matter what value it has.
You have to remove the attribute using removeAttribute to make it editable
const disabled = document.querySelector(".disabled");
const normal = document.querySelector(".normal");
normal.addEventListener("click", () => {
if (disabled.hasAttribute("disabled")) disabled.removeAttribute("disabled");
else disabled.setAttribute("disabled", true);
})
<button disabled class="disabled">button</button>
<button class="normal">toggle</button>
Since you are using React, then you can toggle disabled using state. DEMO
disabled={active}
If active is true then the HTML element is disable else enabled.

why tooltip is not hide on mouseleave event in react?

I am trying to show tooltip on mouse enter and hide on mouse leave.first i make a simple demo which is working fine.
https://codesandbox.io/s/exciting-shannon-4zuij?file=/src/list.js
above code working fine on hover it show's the tooltip and hide on leave.
see same concept i apply on a application.(this code is not working)
https://codesandbox.io/s/cool-liskov-8rvjw?file=/src/App.js
when I hover a item is show the tooltip.but it is not hiding the tooltip when you leaving the item.something went wrong.
const Student = ({students,clickHandler}) => {
console.log(students,"---ee")
const [selectedStudent,setSelectedStudent] = useState(null)
const onMouseHandler = (student,e)=>{
student.visibility = true
setSelectedStudent(student)
}
const onMouseLeaveHandler = (student)=>{
console.log('======',student)
student.visibility = false
setSelectedStudent(student)
}
return (
<ul className="student-container">
{
students && students.length > 0 ? students.map((student,index)=>{
return (
<li key={index} onClick={()=>{
clickHandler(student)
}}
onMouseLeave={()=>{
onMouseLeaveHandler(student)
}}
onMouseEnter={(e)=>{
onMouseHandler(student,e)
}} style={{position:'relative'}}>
<a><span>{student.name}</span></a>
{student.visibility? <ToolTip showToolTip={student.visibility} selectedStudent={selectedStudent}/>:null}
</li>
)
}):null
}
</ul>
);
};
export default Student;
Step too reproduce
Hover on first item Raj
and then try to hover sameer.both tooltip will display.I want only one tooltip will be display which is hovered.
I want my handlers should be in my functional component . I don't want to move these handler to parent component and pass handler as a props
In your demo it's also not work well, - one hide only when open another.
when you set student.visibility you not set state, so nothing has rerendered.
Then when you call setSelectedStudent you pass there just the same referance as was before, since it's the same object, so the state not changed, and again - nothing got rerendered.
What you have to do is pass the updated student in a new variable. like so:
setSelectedStudent({...student})
Then all should work

Only display item hovered on react after map

I am really surprised this isn't working as I anticipated.
I want to show a username when the the person visiting the site hovers over the users's message. Below is the code I have so far. The problem is that when I hover over the message every username in the array that I have mapped over appears. How can I change this so that it is only one username?
I guess I need to change the state to each user's message in a separate component, however, I wouldn't have thought this should be neccessary.
class App extends Component {
state = {
showUsername: false
};
showUsername = () => {
this.setState({
showUsername: true
});
}
hideUsername = () => {
this.setState({
showUsername: false
});
}
render() {
let messageArr = this.props.messages.map(msg => {
let name = `${msg.firstName} ${msg.lastName}`;
return(
<div key={msg.id}>
{this.state.showUsername && (
<span>{name}</span>
)}
<li><span onMouseEnter={this.showUsername} onMouseLeave{this.hideUsername}>{msg.message}</span></li>
</div>
);
});
return (
<ul>
{messageArr}
</ul>
);
}
}
I guess I need to change the state to each user's message in a separate component, however, I wouldn't have thought this should be necessary.
yes.Its necessary.
As of now,there is one common state showUsername is used to show user's name which is why you getting this effect.
showUsername = (oneUserMessage) => {
oneUserMessage.showUsername = true; // updating via reference
}
hideUsername = (oneUserMessage) => {
oneUserMessage.showUsername = false;// updating via reference
}
//return in render
return (
<div key={msg.id}>
{msg.showUsername && (
<span>{name}</span>
)}
<li><span
onMouseEnter={()=>{this.showUsername(msg)}}
onMouseLeave={()=>{this.hideUsername(msg)}}>
{msg.message}</span></li>
</div>
);
Your components are behaving like this, because this.state.showUsername is a state inside your component.
So, whichever div is hovered, it will set the state to true, and all of your others will receive this.state.showUsername as true either.
This makes all of them visible.
What you should do is, write a new component having the div and the logic inside of it. So when you map it down, you each of these divs will have its own states.
Another thing, you can do the map like this:
const messageArr = this.props.messages.map((msg) => <div key={msg.id}>
<li>
<span onMouseEnter={this.showUsername} onMouseLeave{this.hideUsername}>{msg.message}</span>
</li>
</div>);
So no need for the return statement :)

React onClick not firing inside map generated list

I have a simple list being displayed on the return value of a fetch call. I have some functions firing on selection of an item, but for the life of me, I can't fire the onClick event. I have events bound outside the map that work just fine (the onKeyUp and down), but the onClick while inside the map does not work. Not sure where to go from there.
handleClick() {
// won't fire
console.log('test')
}
render() {
return (
<div className="autocomplete">
<input type="text" placeholder={this.props.fieldName} onKeyDown={this.handleKeyDown} onKeyUp={this.handleKeyUp} onClick={this.handleClick} />
<div className="autocomplete__list" onClick={this.handleClick}>
<ul>
{this.state.list.map((item, index) => <li key={index} className={this.checkActive(index)} onClick={this.handleClick}>{item.firstName}</li>)}
</ul>
</div>
</div>
)
}
No errors thrown, the method does nothing when clicking those fields. If it matters at all, the "list" is absolutely positioned. And here is the constructor
constructor(props) {
super(props)
this.state = {
name: '',
list: [],
cursor: 0
}
this.handleKeyDown = this.handleKeyDown.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
this.checkActive = this.checkActive.bind(this)
this.handleClick = this.handleClick.bind(this)
}
The issue seems to be either:
You have the same handler on a parent element that wraps all of your li's.. Usually the click event would bubble upwards from the children to the parent so this is unlikely.
Your li's are a lower z-index than the parent element. Which means that the li elements lay under an element. so a click event only happens on the element on the top.
Try adding this to your css
.autocomplete__list li {
z-index: 100;
}

how to add css classes to a component in react?

So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>

Categories

Resources