React: e.target.getAttribute ("id") works 20% of the time? - javascript

I'm new to React and been trying to fix this for hours. I'm trying to get the id of the button which is clicked But this only gets the id around 20% of the time and the rest it returns nulltext. I have no idea what else to do. I have tried different binding methods but haven't been able to make it work.
I simplified the code here and put it below.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Popupright extends React.Component {
popupnewshow = (e) => {
let ids = e.target.getAttribute("id") + "text";
console.log(ids)
let elements = document.getElementsByClassName('poprighttext showtext');
while(elements.length > 0){
elements[0].classList.remove('showtext');
};
document.getElementById(ids).classList.toggle("showtext");
};
render() {
return (
<div>
<table className="table-bordered">
<tbody>
<tr className="table-samewidth">
<td className="td-general"><button className="popup" id="xxx" onClick={this.popupnewshow}><div className="popuptitle">xxx</div></button></td>
</tr>
<tr className="table-samewidth">
<td className="td-general"><button className="popup" id="yyy" onClick={this.popupnewshow}><div className="popuptitle">yyy</div></button></td>
</tr>
<tr className="table-samewidth">
<td className="td-general"><button className="popup" id="zzz" onClick={this.popupnewshow}><div className="popuptitle">zzz</div></button></td>
</tr>
</tbody>
</table>
<div id="xxxtext" className="poprighttext">
<p>xxx.</p>
</div>
<div id="yyytext" className="poprighttext">
<p>yyy</p>
</div>
<div id="zzztext" className="poprighttext">
<p>zzz</p>
</div>
</div>
);
}
}
export default Popupright;
Console Image: The buttons should give the id xxxtext, yyytext or zzztext depending on the button clicked but this only works 20% of the time. The rest it returns nulltext and after some clicks it returns again the proper id:

Using e.currentTarget.id should solve your issue.
e.target holds the element that you clicked on, but e.currentTarget will hold the element where you have bind the handler.
When you use e.currentTarget:
<button className="popup" id="xxx" onClick={this.popupnewshow}>
<div className="popuptitle">xxx</div><!-- clicking on here:
e.currentTarget.id is xxx -->
</button>
When you use e.target:
<button className="popup" id="xxx" onClick={this.popupnewshow}>
<div className="popuptitle">xxx</div><!-- clicking on here:
there's no id here (the clicked element id) -->
</button>

In general better to avoid direct DOM manipulation like remove. Also you can get the id directly rather than from the event:
const toggleItem = (arrayOfObjects, objectId) => {
//some implementation of toggle object's vislble prop based on id property in array
}
class Popupright extends React.Component {
state = {
popups: [
{id: 'xxx', text: 'xxxtext', visible: false},
{id: 'yyy', text: 'yyytext', visible: false},
...
]
}
togglePopup = id => {
this.setState(prevState => ({
popups: [...toggleItem(prevState.popups, id)]
})
}
render() {
return (
<table>
...
<td>
<button onClick={() => this.togglePopup('xxx')} />
</td>
...
</table>
<div className="popupsWrap">
{this.state.popups.map(popup => {
if (popup.visible) {
return (
<div className="poprighttext">{popup.text}</div>
)
}
}}
</div>
...

Related

React Fuse Component error for default export

I have this code to add div to table onclick, but I have added it from Stack to a Fuse project which has its own template. Please have a look at this code, I think there is a very simple problem with it. I am new to React, and I don't understand, how can I export the class. Whatever I tried, there is an error on it. Here is the code:
const useStyles = makeStyles({
layoutRoot: {},
});
Class Mdf extends React.Component ({
getInitialState: function () {
return {
tablerows: [
{ fname: "Tom", lname: "Moody", age: 23 }
]
};
},
addRow: function () {
// add new data from here
var newdata = { fname: "Tom", lname: "Moody", age: 23 }
//take the existing state and concat the new data and set the state again
this.setState({ tablerows: this.state.tablerows.concat(newdata) });
},
rows: function () {
return this.state.tablerows.map(function (row, i) {
return (<tr key={i}>
<td>{row.fname}</td>
<td>{row.lname}</td>
<td>{row.age}</td>
</tr>);
});
},
render: function () {
const classes = useStyles();
return (
<FusePageSimple
classes={{
root: classes.layoutRoot,
}}
header={
<div className="p-24">
<h1>Site Details</h1>
</div>
}
contentToolbar={
<div className="px-24">
<h4>Content Toolbar22222</h4>
</div>
}
content={
<div className="p-24">
<div>
<table>
<tr>
<td> row 1 </td>
</tr>
<tr>
<td> row 2 </td>
</tr>
<tr>
<td> row 3 </td>
</tr>
{this.rows()}
</table>
<button id="addBtn" onClick={this.addRow}>ADD</button>
</div>
</div>
}
/>
);
}
});
// React.render(<Mdf />)
export default Mdf
And the error message which shows up is this:
Attempted import error: './Mdf' does not contain a default export (imported as 'Mdf').

Vuejs Accessing Data in Modal Component Using Props

There are very similar questions to the one am asking but the solutions proposed don't my situation.
I am trying to access data from a parent list in a different component using a Modal component in vue.
I have tried passing the prop value in the loop as well as the used component in the parent view but receive no data.
This is the parent template.
<template>
<table class="table table-bordered table-stripped" v-if="users.length>0">
<caption>List of Contacts</caption>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(user, index) in users" :key="user.id">
<td>{{index+1}}</td>
<td>{{user.name}}</td>
<td>
<button type="button" class="btn btn-success btn-sm" #click="initEdit(user)" :euser="user">Edit</button>
</td>
</tr>
</tbody>
</table>
<edit-user v-if="showEditUser" :showEditUser.sync="showEdit"></edit-user>
</template>
<script>
import editUser from '#/components/editUser.vue';
export default {
name: 'listusers',
components: {
'edit-user': editUser,
},
data() {
return {
user: [],
users: [],
euser: {},
showEdit: false,
};
},
methods: {
initEdit() {
this.showEditUser = true;
},
},
};
</script>
And this is the modal component.
<template>
<transition name="modal" role="dialog">
<div class="modal" style="display: block">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit Contact</h5>
</div>
<div class="modal-body">
<p>{{euser}}</p>
<p>{{euser.id}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" #click="closeModal">Close</button>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'editUser',
props: {
euser: {
type: Object,
},
showEdit: {
'default' : false,
}
},
data() {
return {
edit_user: [],
};
},
methods: {
closeModal(){
this.$emit('update:showEdit');
},
},
};
</script>
I have tried passing the prop value in the loop as shown above as well as in the component shown below.
<edit-user v-if="showEditUser" :showEditUser.sync="showEdit" :euser="user"></edit-user>
How can I get a single user from the parent to display in the modal ?
In your parent component you can create a data property called "currUser:null" and on "initUser" method you can do the following:
initEdit(user){
this.currUser=user;
this.showEditUser = true;
}
then your modal component definition will look like the following:
<edit-user v-if="showEditUser" :showEditUser.sync="showEdit" :euser="currUser">
</edit-user>
First, you must pass the euser prop to the <edit-user/> component not the button that will call the edit.
Second, the initEdit() function should look more like this
initEdit(user) {
this.user = user
this.showEditUser = true
}
Third, if you plan on editing the user within the modal you will likely need to create a copy of the user within the child component.
watch: {
showEditUser() {
this.editableUser = JSON.parse(JSON.stringify(this.euser))
}
}
then all of the v-models on the child should point to this.editableUser.
when the user goes to save the edit you can emit a new function that could pass the new version back out to the parent like so
saveEdit() {
this.$emit('save', this.editableUser)
}
you would just need to catch the save in the <edit-user /> component like so
<edit-user v-show="showEditUser" :showEditUSer.sync="showEdit" :euser="user" #save="saveUser" />
//script...data...methods
saveUser(user) {
let ind = this.users.map(u => u.id).indexOf(user.id)
if(ind != -1) {
Object.assign(this.users[ind], user) //put user back where it belongs with new data
}
}
Just passing the user like this:
<edit-user v-if="showEditUser" :showEditUser.sync="showEdit" :euser="user[indexOfUser]"></edit-user>
And change the prop properties to receive an object an not an Array
euser: {
type: Object,
},
Just try to add in your main component data to user just for testing if the 'undefined' problem comes from there. and in the :euser="user[0]"
user: [{
id: 1
}]

How to create a computed function in vue.js which consumes a global list and the current item of a foreach?

I have the following HTML code with vue.js bindings. The code shows a list of items in a table.
<table id="test-vue">
<tr v-for="(item, index) in items">
<td>
<input type="text" v-model="item.foreignId" />
</td>
<td>
<div v-if="canApproveItem(item, foreignValues)">
// UI allowing to approve the item
</div>
</td>
</tr>
</table>
The Vue app is defined as:
new Vue({
el: "#test-vue",
data: function() {
return {
items: [
{
foreignId: '4943',
}
],
foreignValues: [
{ id: '2424', canApprove: false },
{ id: '4943', canApprove: true }
],
computed: {
canApproveItem: function(item, foreignValues) {
let foreign = foreignValues(obj => {
return obj.id === item.foreignId;
});
if (foreign) {
return foreign;
} else {
return false;
}
}
}
})
The goal is to show the approve UI only for items which links to a "foreign" whose canApprove property is set to true. The user can edit the foreignId and the user interface should reflect the user-made change of the foreignId values.
If I run the above, the item argument of the canApproveItem function is the Vue object and foreignValues is undefined. I also get a Error in render: "TypeError: canApproveItem is not a function" error in the console.
How to do it properly?
This sounds like a method. You shouldn't pass in foreignValues, as you can access that from the method itself.
<table id="test-vue">
<tr v-for="(item, index) in items">
<td>
<input type="text" v-model="item.foreignId" />
</td>
<td>
<div v-if="canApproveItem(item)">
// UI allowing to approve the item
</div>
</td>
</tr>
</table>
and
new Vue({
// data and stuff here...
methods: {
canApproveItem(item) {
return this.foreignValues.findIndex(obj => obj.id === item.foreignId) !== -1;
}
}
})

React - one event handler to toggle one of multiple similar elements

I'm trying to practice React by rebuilding an agency website. I'm working on a section which has staff images, and clicking one of those images opens the relevant staff bio in a modal. The images and the bios are in separate containing divs.
It feels like I should be able to write one event handler that finds and opens the relevant bio depending on which image is clicked (maybe using something like the data attribute?), but I can't figure out what I'd need to add.
Currently I just have a click handler which toggles a piece of 'active' state. That state is then added as a className to toggle whether the modal is showing. Problem of course being that it doesn't differentiate between bios, so they all show regardless which bio is clicked on.
In case it's useful, here is my 'staff bio' component:
const StaffBio = (props) => {
return (
<div className={`teamMemberOverlay ${props.active}`} onClick={props.onClick}>
<div className="teamMemberExpanded">
<h6>{props.name}</h6>
<div className="seperator"></div>
<p className="title">{props.title}</p>
</div>
</div>
);
}
Which is being used like this:
<StaffBio name="NAME HERE" title="TITLE HERE" active={this.state.active} onClick={this.showBio} />
So far I've got the images set up as follows:
<img src={PaulIllustration} className="staffPhoto" onClick={this.showBio} />
And lastly, my event handler:
showBio() {
let toggle = this.state.active === 'is-active' ? '' : 'is-active';
this.setState({active: toggle});
}
class AboutUsSlider extends Component {
constructor(props) {
super(props);
this.showBio = this.showBio.bind(this)
this.next = this.next.bind(this)
this.state = { active: null }
}
next() {
this.refs.slider.slickNext()
}
showBio(id) {
this.setState({active: id});
}
hideBio(){
this.setState({active: null});
}
render() {
var settings = {...}
const people = [{name: 'Paul', title: 'some title'}, {name: 'Ben', title: 'other'}, ...];
return (
<div>
<Slider ref="slider" {...settings}>
<div className="sliderPage">
<h2>Meet our team</h2>
<div className="seperator"></div>
<div className="teamPhotos">
{ // When setting the index, you should use something unique, I'll use the name here.
people.map((p, index) =>
<img key={p.name} src={`${p.name} + 'Illustration'`} className="staffPhoto" onClick={() => this.showBio(index)}) />
}
</div>
<Button BGColor="#009ECC" text="Our process" onClick={this.next} />
</div>
</Slider>
{ this.state.active && <StaffBio name={people[this.state.active]} title={people[this.state.active].title} onClick={this.hideBio}/>
</div>
)
}
EDITED
There are a couple of things you can do.
Each person probably has an id to identify it. So you could modify your showBio to look like this:
showBio(id) {
this.setState({ active: id })
}
This way, you get which person is currently active in your state.
You also need to change your img
<img src={PaulIllustration} className="staffPhoto" onClick={() => this.showBio(PaulId)} />
Where PaulId would be different for each person.
And your StaffBio:
<StaffBio name="NAME HERE" title="TITLE HERE" active={this.state.active == personId} onClick={this.showBio} />
const StaffBio = (props) => {
return (
<div className={`teamMemberOverlay ${props.active ? 'is-active' : ''}`} onClick={props.onClick}>
<div className="teamMemberExpanded">
<h6>{props.name}</h6>
<div className="seperator"></div>
<p className="title">{props.title}</p>
</div>
</div>
);
}

Outside Click handle event on element

can you tell me how can i handle a click on outside of element .For example i have that
<td><input type="number" className="hour" onKeyDown={this.editInlineHour(this)} /></td>
and i want only when i click outside of that element to execute some function and that is it in react !
I try it with that
window.addEventListener('mousedown', this.pageClick, false);
but even when i click on input field the function is executed.Thanks
This is my full code ..basically it is inline edit on field on table.. when i click on that field it show input field.. and i want when i click outside that field do go away
editInlineHour:function () {
},
showInlineEditHour:function (i) {
this.setState({
index:i,
showInlineHour:true
})
},
showInlineEditStart:function (i) {
this.setState({
index:i,
showInlineStart:true
})
},
showInlineEditEnd:function (i) {
this.setState({
index:i,
showInlineEnd:true
})
},
pageClick:function () {
this.setState({
showInlineStart:false,
showInlineEnd:false,
showInlineHour:false
});
},
render: function () {
var itemMap = this.state.items.map(function (item, i) {
var fieldDp1;
if(this.state.showInlineStart){
fieldDp1 = <input id="inlineDp1" className="flatpickr" data-date-format="m/d/Y"/>
}else{
fieldDp1 = <td onDoubleClick={this.showInlineEditStart.bind(this,i)} ><FormattedDate value={item.startDate}/></td>
}
var fieldDp2;
if(this.state.showInlineEnd){
fieldDp2 = <input id="inlineDp2" className="flatpickr" data-date-format="m/d/Y" />
}else{
fieldDp2 = <td onDoubleClick={this.showInlineEditEnd.bind(this,i)}><FormattedDate value={item.endDate}/></td>
}
var fieldHours;
if(this.state.showInlineHour){
fieldHours = <td><input type="number" className="hour" onKeyDown={this.editInlineHour(this)} /></td>
}else{
fieldHours = <td onDoubleClick={this.showInlineEditHour.bind(this,i)} >{item.workInHours}</td>
}
return (
<tr key={i}>
<td><input type="checkbox" checked={item.selected} onClick={this.handleChange.bind(this,i)} /></td>
{fieldDp1}
{fieldDp2}
{fieldHours}
<td><label type="button" onClick={this.handleShowModal.bind(this,i)}><img height="30px" src="moliv.jpg"/></label>
</td>
</tr>
);
}.bind(this));
return (
<div>
<button className="btn btn-primary center-block" onClick={this.addElem}>Add</button>
{this.state.list.theList.length > 0 ? <button className="btn btn-primary" onClick={this.putResult}>Put result</button> :null}
<table className="table scroll2">
<thead>
<tr className="danger">
<th><input type="checkbox" checked={this.state.allChecked} onClick={this.toggleAll}/></th>
<th>Start Date</th>
<th>End Date</th>
<th>Hours</th>
<th></th>
</tr>
</thead>
<tbody>
{itemMap}
</tbody>
</table>
{this.state.items.length > 0 ? <button type="button" className="btn btn-danger" onClick={this.deleteItems}>Remove</button> : null}
<Modal parentState={this.state} modalPropsId={this.props.theProps.id} handleHideModal={this.handleHideModal}/>
</div>
);
You can add ref to your element and check if click event comes outside of it.
function handleClickOutside(evt) {
if (this.refs.yourInput !== evt.target) {
callSomeFunction();
}
return true;
}
componentDidMount() {
this.clickHandler = handleClickOutside.bind(this);
document.addEventListener('click', this.clickHandler, true);
}
componentWillUnmount() {
document.removeEventListener('click', this.clickHandler, true);
}
In case if you want to check outside click for some component with children, you can use contains
if (!this.refs.yourComponent.contains(evt.target)) {
callSomeFunction();
}
Using that approach you will need to assign a onClick handler to your input element and stop propagation so that it doesn't propagate to the windows onClick event.
const handleInsideClick = (e) => {
e.stopPropagation();
}
React handles events in a smart way, an event is triggered from the deeper node in the DOM to the top, so when you click in the input you can either stop propagation to the onClick handler in the parent node or do whatever you want in the click event and let it propagate.
The event.preventDefault() method stops the default action of an element from happening if that what you're after.
<td><input type="number" id="inputField" className="hour" onKeyDown={this.editInlineHour(this)} /></td>
JavaScript:
$("#inputField").on("submit", function(event){
event.preventDefault(); // Stop event
event.stopPropagation(); // Stop event (react)
});
http://jsbin.com/nejetoxeve/edit?html,js,console,output
You can wrap Component with a component with a position: fixed like so:
import React, { Component } from 'react';
import ReactDom from 'react-dom';
const styles = (onClick) => ({
position: 'fixed',
pointerEvents: onClick ? 'auto' : 'none',
top: 0,
bottom: 0,
right: 0,
left: 0,
height: '100%',
});
export default class FixedLayer extends Component {
static defaultProps = {
zIndex: 2000,
onClick: null,
};
render () {
const divProps = { ...this.props };
delete divProps.zIndex;
return (
<div { ...divProps }
onClick={ (e) => this.props.onClick && (e.target === ReactDom.findDOMNode(this)) && this.props.onClick() }
style={{...this.props.style, ...styles(this.props.onClick), ...{zIndex: this.props.zIndex}}}>
{this.props.children}
</div>
)
}
}

Categories