Long press event in ReactJs - javascript

I have a favorites page where when I single click a button it will redirect to another page but when I hold it, it will have a pop up to remove from favorites how can I achieve that?
This is my favorite page
const FavoritePage = () => {
const getArray = JSON.parse(
localStorage.getItem(favoriteProductsStorageKey) || []
)
return (
<>
<Grid container spacing={3} className={classes.heading}>
<Grid item xs={2}>
<Box pt={0.5}>
<Link to="#">
<ArrowBackIcon className={classes.backSize} />
</Link>
</Box>
</Grid>
<Grid item xs={10}>
<Typography variant="h6" className={classes.header}>
My Favorites
</Typography>
</Grid>
</Grid>
<Box pt={1}>
{getArray.length > 0 ? (
<div className={classes.root}>
{getArray.map((product) => (
<div className="ProductCard" key={product._id}>
<div>
<Link to="product">
<img
className="ProductImage"
src={product.imagePrimary}
alt={product.name}
/>
</Link>
</div>
<div className="ProductCardDetails">
<div className="NameAndPrice">
<div className="ProductName">{product.name}</div>
</div>
</div>
</div>
))}
</div>
) : (
<h4 className={classes.top}>
Add your favorite products here by tapping the{" "}
<span className={classes.icon}>♥</span> symbol on the product.
</h4>
)}
</Box>
</>
)
}
export default FavoritePage
this is the imported function coming from my helpers.js to remove the favorites from local storage
function removeFavorite(product) {
const newFavoriteProducts = favorites.filter(
(iteratedProduct) => iteratedProduct._id !== product._id
)
setFavorites(newFavoriteProducts)
setToLocalStorage(favoriteProductsStorageKey, newFavoriteProducts)
}
I'm trying to find a solution on how to implement the long press to a single product to be removed from the favorites in the local storage.

As far as I am concerned There is no pure html or javascript way to accomplish this. But there is a trick in javascript you use the onmousedown listener and onmouseup listener. Here is a code to do so:
var timeout;
var clicked = function (){
timeout = window.setTimeout(function(){
//Do work here
alert('You clicked the button for 1300');
},1300)//Change time according to need but most commonly used time is 1300 milliseconds.
}
var unclicked = function (){
clearTimeout(timeout);
}
span{
font-size: 130%;
border: 2px solid blue;
}
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div><span onmousedown="clicked();" onmouseup="unclicked();">Click me long!</span> To see the magic.</div>
</body>
</html>
What this code does is very simple it just sets timeout and if it is completed it executes the code.

Implementation using react hooks
codesandbox.io
Full code:
import { useState, useEffect } from "react";
const LongPressButton = (props) => {
const [waitingForLongPress, setWaitingForLongPress] = useState(false);
const [timeoutId, setTimeoutId] = useState(null);
useEffect(() => {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (waitingForLongPress) {
setTimeoutId(
setTimeout(() => {
props.onLongPress();
setWaitingForLongPress(false);
}, props.longPressDelayMS)
);
}
}, [waitingForLongPress]);
return (
<button
onMouseDown={() => setWaitingForLongPress(true)}
onMouseUp={() => setWaitingForLongPress(false)}
onClick={props.onClick}
>
{props.label}
</button>
);
};
export default function App() {
const [longPressCount, setLongPressCount] = useState(0);
const [clickCount, setClickCount] = useState(0);
return (
<div className="App">
<div>Times click: {clickCount}</div>
<div>Times long-pressed: {longPressCount}</div>
<LongPressButton
label="Press me"
onLongPress={() => {
setLongPressCount(longPressCount + 1);
}}
onClick={() => {
setClickCount(clickCount + 1);
}}
longPressDelayMS={1000}
/>
</div>
);
}

Related

react-horizontal-scrolling-menu scrolling error

**Hello, I'm having a problem with react-horizontal-scrolling-menu
, when scrolling, it scrolls to the right a lot, and the rest of the elements disappear, and when you add overflowX: 'scroll' to the BOX, the scroll doesn't work until after the first click
Plus LeftArrow, RightArrow don't work
Take a look at the code**
[error img here]
<Box component="div" sx={{position:'relative', width:'100%',p:'20px'}}>
<HorizontalScrollbar data={bodyParts} bodyParts={bodyParts} bodyPart={bodyPart} setBodyPart={setBodyPart}/>
</Box>
HorizontalScrollba
import React, { useContext } from 'react';
import { ScrollMenu, VisibilityContext } from 'react-horizontal-scrolling-menu';
import { Box, Typography } from '#mui/material';
import BodyPart from './BodyPart';
import RightArrowIcon from '../assets/icons/right-arrow.png';
import LeftArrowIcon from '../assets/icons/left-arrow.png';
const LeftArrow = () => {
const { scrollPrev } = useContext(VisibilityContext);
return (
<Typography onClick={() => scrollPrev()} className="right-arrow">
<img src={LeftArrowIcon} alt="right-arrow" />
</Typography>
);
};
const RightArrow = () => {
const { scrollNext } = useContext(VisibilityContext);
return (
<Typography onClick={() => scrollNext()} className="left-arrow" >
<img src={RightArrowIcon} alt="right-arrow"/>
</Typography>
);
};
const HorizontalScrollbar = ({ data, bodyParts, setBodyPart, bodyPart }) => (
<Box mt={4} sx={{position:'static'}}>
<ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
{data.map((item) => (
<Box
key={item.id || item}
itemId={item.id || item}
title={item.id || item}
m="0 40px"
>
<BodyPart item={item} setBodyPart={setBodyPart} bodyPart={bodyPart} />
</Box>
))}
</ScrollMenu>
</Box>
);
export default HorizontalScrollbar;
install npm i react-horizontal-scrolling-menu#2.7.1, the latest 3.0.1 version has some issues regarding overflow
I was also stuck here but I did a little hack to move on like I just copied the package.json from the git(link was mentioned in the description) and run the command 'npm install' and it start working.
I suggest you also do the same so you can move and after the installation look for an error or the problem
Happy Learning
use Button rather than Typography :
const LeftArrow = () => {
const { scrollPrev } = useContext(VisibilityContext);
return (
<Button onClick={() => scrollPrev()} className="right-arrow">
<img src={LeftArrowIcon} alt="right-arrow" />
</Button>
);
};
This works as intended.

How do I associate seperate state to each button?

Hello
I am trying to associate a like button with each PaperCard component as shown in the code below. I have included the relevant code. Currently, The like button shows up and every time you click it the counter increases BUT all the buttons share the same state. So I am trying to fix that. I am new to JS and React.
Any help will be appreciated. Thanks!
function Home() {
const [likes, setLikes] = useState(0);
const incrementLikes = () => {
const addToLikes = likes + 1;
setLikes(addToLikes)
}
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);}
return (
<div>
<div style={{display:'flex', justifyContent:'center'}}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Grid key={paper.title}>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</Grid>
))}
<div style={{display:'flex', justifyContent: 'center'}}>
<Button variant="contained" onClick={loadMorePapers}>Load More</Button>
</div>
</div>
)
}
The element from the map callback is extracted as a component, and now every button has its own state.
function Home() {
return (
<div>
<div style={{ display: "flex", justifyContent: "center" }}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<LikeButton paper={paper} key={paper.title} />
))}
<div style={{ display: "flex", justifyContent: "center" }}>
<button variant="contained" onClick={loadMorePapers}>Load More</button>
</div>
</div>
);
}
function LikeButton(paper) {
const [likes, setLikes] = useState(0);
const incrementLikes = () => {
const addToLikes = likes + 1;
setLikes(addToLikes);
};
return (
<div key={paper.title}>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</div>
);
}
Create a new functional component called LikeButton (or something relevant) to house the state for each button independently.
In that component, add the state values you want to track per each button. In your case it seems to just be the likes.
So could be something like:
const LikeButton = () => {
const [likes, setLikes] = useState(0); //likes controlled by state of component
const incrementLikes = () => {
setLikes((prevState) => prevState + 1);
};
return <button onClick={incrementLikes}>Likes: {likes}</button>;
};
Then add that component in place of your existing button and remove the state for likes in the Home component. E.g.:
function Home() {
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);
};
return (
<div>
<div style={{ display: "flex", justifyContent: "center" }}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Grid key={paper.title}>
<LikeButton/>
<PaperCard title={paper.title} abstract={paper.abstract} />
</Grid>
))}
<div style={{ display: "flex", justifyContent: "center" }}>
<Button variant="contained" onClick={loadMorePapers}>
Load More
</Button>
</div>
</div>
);
}
Should you want to control state from the Home component, you can pass the likes as props, but it doesn't seem necessary for what you want.
In this situation you should consider using a reusable button component in order to control state within the component itself. Then you do not have to worry about the buttons sharing the same state. Here would be a simple example of a button component that will track it's count independent of the other buttons that are rendered:
import React, { useState } from 'react';
export default function CounterButton() {
const [count, setCount] = useState(0);
function incrementLikes() {
setCount(count + 1);
}
return (
<button onClick={incrementLikes}>
{count} likes
</button>
);
}
You could simply render these buttons like in the pseudo code below:
{[0, 1, 2, 3].map((num: number, index: number) => (
<div key={index}>
<CounterButton />
</div>
))}
I think you're doing too much in one component. The "likes" in your example are for an individual paper, not for the whole site, right?
Maybe something like this...
function Home() {
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);
}
return (
<div>
<div style={{display:'flex', justifyContent:'center'}}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Paper {...paper} key={paper.title} />
))}
<div style={{display:'flex', justifyContent: 'center'}}>
<Button variant="contained" onClick={loadMorePapers}>Load More</Button>
</div>
</div>
);
}
function Paper(props){
const [likes, setLikes] = useState(0);
const incrementLikes = () => setLikes(likes + 1)
return (
<Grid>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</Grid>
)
}
If the data from the api has a key/id you can pass that to your incrementLikes function and use it to increment the likes for the right item.
const [apiData, setApidData] = useState(...)
const incrementLikes = (id) => {
const updated = apiData.map((paper) => {
if (paper.id === id) {
return {
...paper,
likes: paper.likes + 1
};
}
return paper;
});
setApidData(updated);
};
Then pass the id in the button
<button onClick={() => incrementLikes(paper.id)}>Likes: {paper.likes}</button>
// Get a hook function
const { useState } = React;
const PaperCard = ({ title, abstract }) => {
return (
<div>
<p>{title}</p>
<p>{abstract}</p>
</div>
);
};
const Header = () => {
const [apiData, setApidData] = useState([
{
title: 'Testing likes',
id: 1,
likes: 0,
abstract: 'abs',
},
{
title: 'More likes',
id: 3,
likes: 5,
abstract: 'abstract',
}
]);
const incrementLikes = (id) => {
const updated = apiData.map((paper) => {
if (paper.id === id) {
return {
...paper,
likes: paper.likes + 1
};
}
return paper;
});
setApidData(updated);
};
const loadMorePapers = (e) => {};
return (
<div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<h1>Latest Papers</h1>
</div>
{apiData.map((paper) => (
<div key={paper.title}>
<button onClick={() => incrementLikes(paper.id)}>Likes: {paper.likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract} />
</div>
))}
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button variant='contained' onClick={loadMorePapers}>
Load More
</button>
</div>
</div>
);
};
// Render it
ReactDOM.render(<Header />, document.getElementById('react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

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

Close material-ui popper when on clickAway

I have a material ui popper and I am trying to make it close when I click outside of the popper using
ClickAwayListener, but I cannot get this to work. I added the ClickAwayListener around the popper and tried adding it around the content in the popper but nothing seams to work.
I am really new to material-ui so I am a bit lost on how this should be done
This is my code
const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
// const open = Boolean(anchorEl);
const handleClickAway = () => {
setAnchorEl(false);
};
const experience = (img, title, id, popoverCategory) => (
<div
className="experience"
aria-describedby={id}
id={id}
onClick={handleClick}
onKeyDown={handleClick}
role="button"
tabIndex="0"
>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
<ClickAwayListener onClickAway={handleClickAway}>
<Popper
id={id}
open={anchorEl && anchorEl.id === id}
anchorEl={anchorEl}
className={clsx(classes[id])}
modifiers={{
flip: {
enabled: false,
},
}}
>
<Button >x</Button>
<div className={clsx(classes.paper)}>
{
popoverCategory.map(url => (
<img
key={id}
data-sizes="auto"
className="lazyload"
src={url}
alt={title}
/>
))
}
</div>
</Popper>
</ClickAwayListener>
</div>
);
You may toggle your <Popper /> component visibility using the variable in the local state of the parent component and pass it down as a prop:
//dependencies
const { render } = ReactDOM,
{ useState } = React,
{ Popper, Button, Paper, ClickAwayListener } = MaterialUI
//custom popper
const MyPopper = ({isOpen,clickAwayHandler}) => (
<ClickAwayListener onClickAway={clickAwayHandler}>
<Popper open={isOpen}>
<Paper className="popper">There goes my custom popper</Paper>
</Popper>
</ClickAwayListener>
)
//main page
const MainPage = () => {
const [isOpen, setIsOpen] = useState(true),
clickAwayHandler = () => setIsOpen(false),
clickHandler = () => setIsOpen(true)
return (
<div>
<Button onClick={clickHandler}>Toggle pop-up</Button>
{
isOpen && <MyPopper {...{clickAwayHandler,isOpen}} />
}
</div>
)
}
//render
render (
<MainPage />,
document.getElementById('root')
)
.popper {
display: block;
position: relative;
top: 50px;
left: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script><div id="root"></div>
It is not working because por the Popper component is a Portal and with portals you have to use de ClickAwayListener in a small different way.
You can check the example at MUI documentation but the short summary is that the button that opens the popper has to be inside the ClickAwayListener too and you have to check if it's open before rendering the Popper.
const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const open = Boolean(anchorEl);
const handleClickAway = () => {
setAnchorEl(false);
};
const experience = (img, title, id, popoverCategory) => (
<ClickAwayListener onClickAway={handleClickAway}> // This wraps all
<div
className="experience"
aria-describedby={id}
id={id}
onClick={handleClick}
onKeyDown={handleClick}
role="button"
tabIndex="0"
>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
{open ? ( // You do the check here
<Popper
id={id}
open={anchorEl && anchorEl.id === id}
anchorEl={anchorEl}
className={clsx(classes[id])}
modifiers={{
flip: {
enabled: false,
},
}}
>
<Button >x</Button>
<div className={clsx(classes.paper)}>
{
popoverCategory.map(url => (
<img
key={id}
data-sizes="auto"
className="lazyload"
src={url}
alt={title}
/>
))
}
</div>
</Popper>
) : null}
</div>
</ClickAwayListener>
);
Perhaps MUI has created a new component that handles this for us since the time of this post.
Popover Component: https://mui.com/material-ui/react-popover/
It works similar to the Modal component that handles on click out for you.
Hope this helps.

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