Getting the element that was clicked in functional component in React - javascript

I'm new to React and can't get the clicked element.
"this" in functional component doesn't work
function Test(data) {
function getElement() {
}
return (
<div>
{data.map((option: any, index: any) => (
<Text
key={index}
onClick={() => getElement()}
>
{option}
</Text>
))}
</div>
)
}
there are several elements in the data that I want to switch one by one, changing the class 'active', but it is not possible to get the element that was clicked

Be sure to pass the event to your click handler:
function Test(data) {
const handleClick = e => {
const el = e.target
console.log(el)
}
return (
<div>
{data.map((option: any, index: any) => (
<Text
key={index}
onClick={handleClick}
>
{option}
</Text>
))}
</div>
)
}

Related

Show one div and hide another in an array with onMouseEnter in React

I have an array of items that i want to show with a map function, and every item is shown as a card.
I'm trying to show two kinds of cards with a different content, one if "isHover" is false, and the other if it true using onMouseEnter/onMouseOver.
I made "isHover" as an array in order to know which item to show/hide.
(The "isHover" array has the same length that the items' array has).
The problem is that when I hover one card it dissappears and nothing is shown in place of it. :(
The code:
function TeachersShow(props) {
const [isHover, setIsHover] = useState(null);
const updateIsHover = (index, isHover1) => {
let newArray = isHover;
newArray[index] = isHover1;
setIsHover([...newArray]);
console.log(isHover[index]);
};
return (
<div>
{isHover[index] === false && (<Card className="teacher-card"
onMouseEnter={() => { updateIsHover(index, true) }}
key={index}
item={item}
onClick={() => navigateToTeacher(item)}
>
<Card.Img className="teachersImg" src={item.photoURL}>
</Card.Img>
<Card.Title className=" teachersName">
{item.username}
</Card.Title>
</Card>)}
{isHover[index] === true && (
<Card className="card-hover"
onMouseleave={() => { updateIsHover(index, false) }}
key={index}
item={item}
onClick={() => navigateToTeacher(item)}
>
<Card.Title className=" teachersName">
{item.username}
</Card.Title>
<Card.Subtitle className="proTeacher">
{`${item.profession} teacher`}
</Card.Subtitle>
<Card.Text className="teacherDesc">
{item.teacher_description}
</Card.Text>
</Card>)}
</Col>
))}
<Col></Col>
</Row>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(TeachersShow);
It's little hard to tell what's wrong, since a lot of information is missing from the code. But try to create a component Let's say <Teacher />, and let it be responsible for hovering action. Try this:
function Teacher(item) {
const [hover, setHover] = useState(false);
const renderCardData = () => {
if (!hover) {
return (
<Card.Img className="teachersImg" src={item.photoURL} />
<Card.Title className="teachersName">
{item.username}
</Card.Title>
);
}
return (
<Card.Title className=" teachersName">
{ item.username }
</Card.Title>
<Card.Subtitle className="proTeacher">
{ `${item.profession} teacher` }
</Card.Subtitle>
<Card.Text className="teacherDesc">
{ item.teacher_description }
</Card.Text>
);
};
return (
<Card
className={ hover
? 'card-hover'
: 'teacher-card' }
onMouseEnter={ () => setHover(true) }
onMouseLeave={ () => setHover(false) }
>
{ renderCardData() }
</Card>
);
}
export default Teacher;
And you render it like that:
function TeachersList(teachers) {
return teachers.map(Teacher);
};

React recursion - can't get correct value

I'm having Array of Objects named items. Each Object looks like this
interface IItem {
id: string;
title: string;
items: IItem[] | null
}
So I'm doing recursion, as in
parent component:
{items?.map((folder, index) => (
<Item
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
console.log(
event.target.innerHTML,
folder.id,
folder.title
);
}}
key={folder.id}
data={folder}
/>
))}
WhileItem component looks like this:
const nestedItems =
data.items
? data.items.map((item, index) => (
<Item
onClick={onClick}
key={item.id}
data={item}
/>
))
: null;
return (
<div>
<button onClick={onClick}>
<Typography>
{data.title} {data.id}
</Typography>
</button>
<div>{nestedItems}</div>
</div>
Html output and whats rendered in browser looks correct,
but when I try to get {id}, or {title} from item in parent component
the output of any click which has parent doesn't log it's own id, but it's parent id.
The strange thing is that console.log from above on any "not first level items" looks like this:
event.target.innerHTML - correct value
folder.id, folder.title - has parent ? parent id value : it's own id value
So I'm not really sure what's happening and where is the catch :/
Thanks in advance!
You only define onClick for each of the root-level Items and then within the Item component you just pass the onClick prop to the child. Thus every child has the same onClick function as its parent.
To fix this you can change onClick to be more generic by accepting data (the id and/or other information) rather than having data curried into it. This way the onClick function passed to the Item works for any item, but the onClick function passed to the button within each Item is unique to that item.
For example:
Parent component:
{items?.map((folder, index) => (
<Item
onClick={(event, clickedFolder) => {
event.stopPropagation();
event.preventDefault();
console.log(
event.target.innerHTML,
clickedFolder.id,
clickedFolder.title
);
}}
key={folder.id}
data={folder}
/>
))}
Item component:
const nestedItems =
data.items
? data.items.map((item, index) => (
<Item
onClick={onClick}
key={item.id}
data={item}
/>
))
: null;
return (
<div>
<button onClick={event => onClick(event, data)}>
<Typography>
{data.title} {data.id}
</Typography>
</button>
<div>{nestedItems}</div>
</div>
)

React class prop onclick

This is my scenario
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} button className={classes.mainTagItem}>
<div className={classes.mainTagCircle}></div>
<ListItemText
primary={mainTagItem.tag.name}
/>
</ListItem>
)
})}
</List>
when i click on my ListItem ( that becomes selected ) i want the element <div className={classes.mainTagCircle}> has an active class
For Example:
<div classes={{ root: !!listItemSelected ? classes.mainTagCircleActive : classes.mainTagCircle, }}></div>
I have already a method onClick in my ListItem, how can i apply this logic?
given that you have a mainTag state you could compare with your element tagId to define which class to select. If it's the same as your state then active class wil be returned:
<div className={
mainTag === mainTagItem.tag.tagId
? classes.mainTagCircleActive
: classes.mainTagCircle}>
</div>
Solution with a library
You could try with this library clsx. Then do something like this:
function Component() {
const [mainTag, setMainTag] = useState(null);
return (
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} className=
{clsx([classes.mainTagItem, mainTag === mainTagItem.tag.tagId ? :
'activeClass': 'defaultClass' ])}>
<div className={classes.mainTagCircle}></div>
<ListItemText
primary={mainTagItem.tag.name}
/>
</ListItem>
)
})}
</List>
)
}
Solution without libraries
function Component() {
const [mainTag, setMainTag] = useState(null);
return (
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} className={
mainTag === mainTagItem.tag.tagId
? classes.mainTagCircleActive
: classes.mainTagCircle}
/>
</ListItem>
)
})}
</List>
)
}

Invalid Hook Call - React Hooks

I'm really new to JS and React. I get this error:
Invalid Hook Call
when I try to make a component appear and disappear when another component is clicked. This is my code:
const RenderList = ({data}) => {
return data.map((option, index) => {
return <Item title={option}/>
});
};
const Header = ({ title, style, press }) => (
<TouchableHighlight onPress={press}>
<Text style={style} >{title}</Text>
</TouchableHighlight>
)
const RenderItem = ( {item} ) => {
console.log(styles)
let dataToShow;
const [listState, setListState] = useState(true);
if (listState){
dataToShow = <RenderList data={item.data}/>
} else {
dataToShow = <Text/>
}
return (
<View style={styles.section}>
<Header title={item.title} style={styles.header} press={setListState(!listState)}/>
{dataToShow}
</View>
)}
EDIT
RenderItem is used in a flat list element as a function. (From what I understand)
const SettingsSection = (props) => {
const db = props.data;
return(
<View>
<FlatList
style={styles.sectionList}
data={db}
renderItem={RenderItem}
keyExtractor={item=>item.title}
ItemSeparatorComponent={FlatListItemSeparator}
/>
</View>
);
}
renderItem, as the name suggests, is a render prop, and as such is called directly (like so: renderItem({item})), not instantiated as a component (like so: <RenderItem item={item}/>).
This translates to React not creating the appropriate rendering "context" for hooks to work. You can make sure your RenderItem function is instantiated as a component by using it like this on the render prop:
<FlatList
style={styles.sectionList}
data={db}
renderItem={item => <RenderItem {...item}/>} // see here!
keyExtractor={item=>item.title}
ItemSeparatorComponent={FlatListItemSeparator}
/>
That way, RenderItem is treated as a component and thus can use hooks.
I think problem is occurring due to setListState(!listState) with press. I suggest you to wrap your state changing method into a function. Because onPress accepts only function type but you are giving it a return statement from hooks.
const RenderList = ({data}) => {
return data.map((option, index) => {
return <Item title={option}/>
});
};
const Header = ({ title, style, press }) => (
<TouchableHighlight onPress={press}>
<Text style={style} >{title}</Text>
</TouchableHighlight>
)
const RenderItem = ( {item} ) => {
console.log(styles)
let dataToShow;
const [listState, setListState] = useState(true);
if (listState){
dataToShow = <RenderList data={item.data}/>
} else {
dataToShow = <Text/>
}
return (
<View style={styles.section}>
<Header
title={item.title}
style={styles.header}
press={()=>{
setListState(!listState)
}}
/>
{dataToShow}
</View>
)}

React give animation only to clicked element

I wish to add spinner animation after clicking on button, when get response, spinner is supposed to disappear. So far works fine but the problem is that I render list with many elements and every element has own delete button, while clicking on one, animation is added to all elements of the list. I wish it to appear only once, next to this particular clicked element of the list.
const displayCertificateList = (
classes,
mainStatus,
handleDeleteSingleCertificate,
animateDelete
) => {
return mainStatus.map((el, i) => {
return (
<div className={classes.certificatesListContainer} style={{border:'none'}}>
<List key={i} style={{padding: '10px'}}>
<ListItem style={{ padding: "0 0 0 20px" }}>
<ListItemText
className={classes.certificatesList}
primary={
<Typography type="body2" style={{ fontWeight: "bold" }} className={classes.certificatesListFont}>
Valid until:
</Typography>
}
secondary={
<Typography
type="body2"
className={classNames(
classes.certificatesListSecondArgument,
classes.certificatesListFont,
el.expiresIn > 90 ? classes.green : classes.red
)}
>
{el.validUntil.slice(0,9)} ({el.expiresIn} days)
</Typography>
}
/>
</ListItem>
</List>
<div className={classes.certificatesBtn}>
<Button
variant="contained"
size="small"
color="secondary"
className={classes.button}
onClick={() => {
if (
window.confirm(
`Are you really sure?
)
)
handleDeleteSingleCertificate(el, i);
}}
>
<DeleteIcon className={classes.leftIcon} />
Delete
</Button>
<div style={{left: '-50%',top: '30%'}} className={classNames(animateDelete ? classes.spinner : null)}></div>
</div>
</div>
);
});
} else {
return (
<div>
<Typography component="h1" variant="h6">
The applet is not innitialized, please initialize it first
</Typography>
</div>
);
};
And in parent component:
handleDeleteSingleCertificate = (el, i) => {
this.setState({animatingDelete: true})
this.make_call(
this.state.selected,
(res) => {
console.log(res)
this.setState({animatingDelete: false})
}
)
}
And pass it like this:
{this.state.view === 'certificates' && this.state.certificates && displayCertificates(classes, fakeData, this.handleDeleteSingleCertificate, this.state.animatingDelete)}
I suggest to make displayCertificateList function component to stateful component and store the animatingDelete in it - `cause it is the state of that particular item in deed.
class ListItem extends React.Component {
state = {
isDeleting: false
}
handleDelete = () => {
const { onDelete, id } = this.props;
onDelete(id);
this.setState({
isDeleting: true
})
}
render(){
const { isDeleting } = this.state;
return (
<li>
<button onClick={this.handleDelete}>Delete {isDeleting && '(spinner)'}</button>
</li>
)
}
}
class List extends React.Component {
state = {
listItems: [
{id: 1},
{id: 2}
]
}
handleDelete = id => {
console.log('delete ' + id);
// do the async operation here and remove the item from state
}
render(){
const { listItems } = this.state;
return (
<ul>
{listItems.map(({id}) => (
<ListItem id={id} key={id} onDelete={this.handleDelete} />
))}
</ul>
)
}
}
ReactDOM.render(<List />, document.getElementById('root'));
<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" />
In my opinion, it's better to use count instead of animatingDelete to mark. You can plus 1 when click on the delete button and then when it's done minus 1. when count equals to 0, hide spining otherwise show it.

Categories