Adding and removing className dynamically in an inifinite loop - reactjs

I built a carousel that displays 3 slides. While the center one is 100% width displayed, the left and right ones are only visible 10% of the width.
After accessing the website, the carousel starts moving automatically using this:
componentDidMount() {
this.carouselTimer = setInterval(() => {
this.handleButtonNext();
}, 5000);
}
And if I manually change the slide, I reset the interval that changes the slides automatically using clearInterval and setInterval again.
In order to be able to add a sliding animation, I want to change the state properties (leftSlide & rightSlide) from false to true and after the animation back to false.
I tried to change the properties from false to true inside handleButtonNext() method making changes here:
<Slide
className={` ${this.state.leftSlide ? ' left-slide' : ''} ${this.state.rightSlide ? 'right-slide' : ''}`}
...the rest of slides.../>
The dilemma I have and the problem I encountered so far is that I cannot remove the added class in such a manner that it won't break the autoplay feature.
I tried using a reset method and restarting the autoplay, but no solution seems to be working.
Without the removal of the added class, the autoplay (and reset in case of a manual change of the slides) works just fine, but that's not enough.
This is the method that handles next button:
handleButtonNext() {
this.setState({
rightSlide: true
});
// this.wait1ms = setInterval(() => {
// }, 1100); (useless attempt)
this.setState({
activeSlide: this.nextSlide()
})
}
nextSlide() {
let nextIndex = this.state.activeSlide+1;
return (nextIndex>this.state.slides.length-1) ? 0 : nextIndex ;
}
*The method is used here:*
<a className="button-container right">
<div className="carousel-button next" onClick={this.handleButtonNext}></div>
</a>
#same for the left button
I need to mention that I do not master React and I am fairly new to it. Thank you for the time you will take to help me! I wish you a great day.
L.E: I forgot to mention that I would like to do this using class component, not the hooks that function provides.

The problem is that I cannot remove the added class in such a manner that it won't break the autoplay feature.
setNextSlideStates(){
this.setState({
// remove rightSlide state
rightSlide: false,
// update the active slide state
activeSlide: this.nextSlide()
});
}
// for usert button click handler, set withAnimation to false.
handleButtonNext(withAnimation) {
if(this.carouselTimer) clearTimeout(this.carouselTimer)
// if there are any animations, terminate them and call setNextSlideStates manually
if(this.animationTimer) {clearTimeout(this.animationTimer); this.setNextSlideStates(); }
// start the animation
this.setState({
rightSlide: true
});
// wait 1.1 sec for animation to end
this.animationTimer = setTimeout(() => {
this.setNextSlideStates()
this.animationTimer = null;
// autoplay the next slide
this.carouselTimer = setTimeout(() => {
this.handleButtonNext(true);
}, 3900);
}, withAnimation ? 1100 : 0)
}
Also change your componentDidMount to:
componentDidMount() {
this.carouselTimer = setTimeout(() => {
this.handleButtonNext(true);
}, 3900);
}
Button Handler:
{/* no animation */}
<button onClick={() => {this.handleButtonNext(false)}}>next</button>

Related

Why does #use-gesture stop sending state updates when mouse is outside of containing div?

I'm using #use-gesture/react to drag an element around. I have an element on a page with the useDrag bind hook attached:
<Grid item xs={3}>
<div {...eventHandlers}> // <- memo-ized hook
<ColumnDragCard
isShadow={!(!dragState && !(dragState ? false : false))}
isDraggable={!dragState && !(dragState ? false : false)}
log={true}
/>
</div>
<ColumnDragObject dragState={dragState} />
</Grid>
When the useDrag start event fires on the <div>, dragState is set, and the <ColumnDragObject creates a portal with another <ColumnDragCard>:
const dragObjectPortal =
dragState && dragState.pointerStartInfo
? createPortal(
<div style={dragObjectStyle}>
<div style={dragObjectPositionerStyle} ref={dragBoxRef}>
<div style={dragObjectHolderStyle}>
<ColumnDragCard isDragged />
</div>
</div>
</div>,
document.body
)
: null;
I then update the position of the copied card, based on the xy reported from the hook:
useLayoutEffect(() => {
if (dragState) {
dragState.updateListeners["dragObj"] = (xy, elapsedTime) => {
if (!dragBoxRef.current) {
return;
}
console.log(`Mouse is at x:${xy[0]}, y:${xy[0]}`);
console.log(`Time since start: ${elapsedTime}`);
dragBoxRef.current.style.left = `${xy[0]}px`;
dragBoxRef.current.style.top = `${xy[1]}px`;
};
}
}, [dragState]);
The problem I'm having is, when the pointer is moved outside of what appears to be the original <div>, useDrag stops reporting state changes. I have it in a codesandbox. You can see that when you move the mouse outside of a certain range, the console.logs stops reporting. But, if you move it back inside and release, the log of elapsedTime indicates the hook was listening the whole time ... it just stopped reporting back.
Any ideas on how I fix this?

Skipping through a video with a single keypress

I have a React video player project and I have a way of skipping the video by 1 second when a keyDown event occurs. This is effective because if you hold the key down multiple events are fired so the video continues to skip at e.g. 5-second intervals up until the user releases the key.
I now want to implement it differently so when a user taps the key down once and then, even if they lift their finger the video carries on skipping until the user presses the key down once again (so they don't need to hold the key down)
How do I do this? This is a section of my code so far. It's actually for a Smart TV APP so any reference you read regards focus is just the navigation pattern for selecting divs with your TV remote. The main bit of code is inside the keyDown function that then calls handleSkipForward or handleSkipBack. Also the videoElement ref is shared across functional components using store.VideoElement.
I was thinking of something like having a boolean state hook for skipping but when that state is true how do I repeatedly request a skip of 5 seconds or is there something within the video element that you can just set to progress through a video at a set rate?
focusKey,
elementRef: containerRef,
onKeyDown: (event) => {
// if (!playerControlsVisible) {
// event.stopPropagation()
// event.preventDefault()
// return
// }
if (event.key === "Enter" || event.key === " ") {
skipDirection === "Forward"
? handleSkipForwardAction()
: handleSkipBackwardAction()
}
},
onFocus: (event) => {},
})
function handleSkipForwardAction() {
if (store.videoElement.currentTime + skipTime < duration) {
store.videoElement.currentTime += skipTime
} else if (store.videoElement.currentTime < duration) {
store.videoElement.currentTime = duration
} else return
}
function handleSkipBackwardAction() {
if (store.videoElement.currentTime - skipTime > 0) {
store.videoElement.currentTime -= skipTime
} else if (store.videoElement.currentTime > 0) {
store.videoElement.currentTime = 0
} else return
}```
It should be simple. What you need to do is implement the setInterval function.
You can just add an interval (infinite loop) and store the Interval ID on a state so that you can stop that infinite loop using the clearInterval function.
onKeyDown: (event) => {
if (event.key === "Enter" || event.key === " ") {
if (moveForward){
clearInterval(moveForward);
// Assuming you use Functional Component and `useState` hooks.
setMoveForward(null);
return;
}
const action = skipDirection === "Forward"
? handleSkipForwardAction
: handleSkipBackwardAction;
setMoveForward(setInterval(action,100));
// 100 is in milisecond. so 1000 = 1 second.
// 100 = 10 function call in 1 second. Meaning 6 second skip/second.
// You can adjust the calculation on your own.
}
},
This is not tested, so do let me know if it works or not. But the idea is to use a looping function when the user clicks the specific button and stop the looping function when the user clicks the specific button again.
References:
setInterval
clearInterval

React list, how does if else exactly work?

so in my React App, I basically have three buttons. When the specific button is clicked, I want to update the clicked value to be true. I also want the rest of the items that weren't clicked to be false. Is there another way to target the elements that weren't clicked on? I got this solution, but am confused on how it exactly works. I thought that if the first if statement returned true, the else if wouldn't run? So can someone explain how these are both running?
class App extends React.Component {
// state
state = {
list: this.props.tabs,
currentTabContent: '',
};
// event handlers
onButtonClick(tab) {
// ======THIS IS WHAT I DON'T UNDERSTAND========
const newList = this.state.list.map((item) => {
if (item === tab) {
item.clicked = true;
} else if (item !== tab) {
item.clicked = false;
}
return item;
});
// ==============================================
this.setState({
currentTabContent: tab.content,
list: newList,
});
}
// helpers
renderButtons() {
return this.props.tabs.map((tab, index) => (
<li key={index}>
<button
className={tab.clicked ? 'offset' : null}
onClick={() => this.onButtonClick(tab)}
>
{tab.name}
</button>
</li>
));
}
renderContent() {
return this.state.currentTabContent;
}
render() {
return (
<div className="App">
<ul>{this.renderButtons()}</ul>
<div className="display">{this.renderContent()}</div>
</div>
);
}
}
export default App;
I think your misunderstanding lies more in not quite understanding if...else if rather than anything to do with React. Let's take a look at your condition:
if (item === tab) {
item.clicked = true;
} else if (item !== tab) {
item.clicked = false;
}
return item;
This function runs when the following is called by the button's click handler:
() => this.onButtonClick(tab)
Where tab is a specific object corresponding to a specific button. You then map over list in state, which just appears to be the same list of tabs. For each object it checks if tab === listItem if that is true the stuff in the first block executes, that's why the correct button gets set to true. It then does not evaluate the second condition for that item, and just returns the item.
It then moves on to the other items, who will not be equal to tab, and they evaluate in the second condition, so they are marked as false for clicked.
There are some much more worrisome and larger issues in your code here that have more to do with you making comparisons between objects and the dataflow of your components, but those aren't the subject of your question here, I just wanted to warn you to look out for them in the future.

React Native: state is null sometimes - setState is working asynchronously

I think I do have some problems with the state of my application . I already figured out that this.setState({ ... }) is a function which is working asynchronously.. So, I think this has something to do with my problem.
My problem is that I want to show a dialog popup to my user when I am sending a push notification via OneSignal. This push notification gets received by both iOS and Android. Even when the app is running in the
background, foreground or got killed and isn't even running in the background. For the popup dialog I am using this package: react-native-popup-dialog
This popup is only visible if I send certain key/value pairs with the push notification. These keys are:
showPopup:true - Displaying the popup when true. If it isn't set or not equals true, it isn't displayed!
openLink:mydomain.de- Adds a button with a link to the popup
buttonText:Open in Browser - Sets the button text to the link
Note, the extra URL button is only added to the popup if key openLink and buttonText is set. Of none of them or only one of the key is set, it isn't displaying this button.
However, the popup dialog only shows up sometimes in some cases. I will list them for you below:
Case 1: The application is opened. In this case the popup shows up on iOS and Android. This gets handled by the
onReceived function!
Case 2: The app is completely close (swiped off the screen/killed). In this case, the popup shows up on Android
devices but not on iOS devices! This gets handled by the onOpened function!
Case 3: The app has been opened and is now running in the background. In this case, the popup shows up on iOS
devices but not on Android devices. This gets handled by the onOpened function too!
So, cause I am not getting and error messages or something else, I guess I am right with my guess that this issue is
due the asynchronous this.setState({ ... }) function.
My question now is how can I make sure that the state of notification and visible is always set before rendering the getPopup(...) method.. I already was thinking about implementing it so that I call the getPopup(...) function with parameters. So, I can be sure the parameters are always set before calling the method. However, sadly this is not possible. Cause the class you see below, the SuperScreen class, is just a class which gets extended by some subclasses to bundle my code like the push notification code or some functions I need in every of these subclasses.
Also, I already tried out to add a variable to my SuperClass state e.g. called stateSet which gets set after the setState({ ... }) function of either onReceived or onOpened has finished and verify it with
if(this.state.stateSet) in the first line of the getPopup(...) function. However, this is also not possible. The reason for that is because then my popup is not closing anymore when I am pressing either Ok or the link button.
If you guys have any ideas on how to solve this problem I really would appreciate it!
Here is my code:
export default class SuperScreen extends Component {
constructor(props) {
super(props);
this.state = {
pushNotification: null,
visible: false
};
OneSignal.init("00000000", {
kOSSettingsKeyAutoPrompt: true
});
OneSignal.inFocusDisplaying(0);
OneSignal.enableVibrate(true);
OneSignal.enableSound(true);
OneSignal.addEventListener("received", this.onReceived);
OneSignal.addEventListener("opened", this.onOpened);
OneSignal.addEventListener("ids", this.onIds);
}
componentWillUnmount() {
OneSignal.removeEventListener("received", this.onReceived);
OneSignal.removeEventListener("opened", this.onOpened);
OneSignal.removeEventListener("ids", this.onIds);
}
onReceived = notification => {
//App is opened!
console.log("Notification received: ", notification);
this.setState({
pushNotification: notification,
visible: true
});
if (notification.payload.notificationID != null) {
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: notification.payload.notificationID,
clicked: true
});
}
};
onOpened = openResult => {
//App either is closed or running in background
//Android: Closed: Showing Background: Not Showing
//iOS: Closed: Not Showing Background: Showing)
console.log("openResult: ", openResult);
this.setState({
pushNotification: openResult.notification,
visible: true
});
if (openResult.notification.payload.notificationID != null) {
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: openResult.notification.payload.notificationID,
clicked: true
});
}
};
onIds = device => {
console.log("Device info: ", device);
};
getPopup() {
if (
this.state.pushNotification != null &&
this.state.pushNotification.payload.additionalData != null &&
this.state.pushNotification.payload.additionalData.showPopup != null &&
this.state.pushNotification.payload.additionalData.showPopup == "true"
) {
var actionButtons = null;
if (
this.state.pushNotification.payload.additionalData.openLink != null &&
this.state.pushNotification.payload.additionalData.buttonText != null
) {
actionButtons = [
<DialogButton
text="Ok"
key={0}
onPress={() => {
this.setState({ visible: false });
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: this.state.pushNotification.payload
.notificationID,
opened: false
});
}}
/>
];
actionButtons.push(
<DialogButton
text={this.state.pushNotification.payload.additionalData.buttonText}
key={1}
onPress={() => {
this.openLink(
this.state.pushNotification.payload.additionalData.openLink
);
this.setState({ visible: false });
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: this.state.pushNotification.payload
.notificationID,
link: this.state.pushNotification.payload.additionalData
.openLink,
opened: true
});
}}
/>
);
} else {
actionButtons = [
<DialogButton
text="Ok"
key={0}
onPress={() => {
this.setState({ visible: false });
firebase.analytics().logEvent("Popup_Link_Button", {
popupID: this.state.pushNotification.payload.notificationID,
opened: false
});
}}
/>
];
}
return (
<Dialog
visible={this.state.visible}
dialogTitle={
<DialogTitle
title={
this.state.pushNotification == null
? ""
: this.state.pushNotification.payload.title
}
/>
}
dialogAnimation={
new SlideAnimation({
slideFrom: "bottom"
})
}
dialogStyle={{ marginLeft: 20, marginRight: 20 }}
actions={actionButtons}
>
<DialogContent>
<Text />
<Text>
{this.state.pushNotification == null
? ""
: this.state.pushNotification.payload.body}
</Text>
</DialogContent>
</Dialog>
);
}
}
you can add a callback to setState to ensure the code runs after setting the state:
this.setState().then({ //Do something here. })
Use async, await for asynchronously .
onReceived = async (notification) => {
//App is opened!
console.log("Notification received: ", notification);
await this.setState({ // It will wait until finish setState.
pushNotification: notification,
visible: true
});
if (notification.payload.notificationID != null) {
firebase.analytics().logEvent("Popup_Link_Button", {
notificationID: notification.payload.notificationID,
clicked: true
});
}
};
You can use a callback in setState!, i learned from it a month ago and it's been useful since . Check this article , you can pass a function as a callback ;)
this.setState(
{ pushNotification: notification,
visible: true }, () => {this.getPopup()}) //this.getPopup it's the second parameter, a callback
Check the article, it's short and will help

Pulling the next row of data from a dynamic table into a dialog in ReactJS

I have a table that displays a number of records from a json (dynamically). When you click on the desired row, you get a dialog with actions within it (records count, reject, approve, close dialog, next record, and previous record).
I have pretty much all of it working except the next and prev functionality. I thought a ++ and -- on the rowIndex would fetch the next iteration (this is my angularJS thought process by the way, since I created this already in Angular but we are converting to ReactJS), but obviously, it doesn't work that way.
These are the functions I created, however, you can see a minimalist code snippet in my webpackbin that you can use to play around with it:
//Pull the next item
handleApprovalDialogNext = (rowIndex, openIndex, event) => {
this.setState(
{
openIndex: rowIndex++,
}, () => {this.setState({ open: true })}
)
};
//Pull the prev item
handleApprovalDialogPrev = (rowIndex, openIndex, event) => {
this.setState(
{
openIndex: rowIndex--,
}, () => {this.setState({ open: true })}
)
};
Here is a link to my webpackbin
p.s. Ideally, if the item is the first one, it shouldn't allow you to go back (perhaps, disabling the arrow icon). Same thing when you reach the end of the data.
Any help would be really appreciated.
You're webpackbin is getting errors and won't load. However, I think you should be adding/subtracting from this.state.openIndex rather than rowIndex. Not sure why you are setting open in the callback. Setstate callback is discouraged in the React docs https://facebook.github.io/react/docs/react-component.html#setstate.
//Pull the next item
handleApprovalDialogNext = (rowIndex, openIndex, event) => {
this.setState({
openIndex: this.state.openIndex + 1,
open: true
});
};
//Pull the prev item
handleApprovalDialogPrev = (rowIndex, openIndex, event) => {
this.setState({
openIndex: this.state.openIndex - 1,
open: true
});
};
Also, check out How to uses increment operator in React to know why you should use + 1 rather than ++.
To render the icons conditionally, you just need to check if the current openIndex is the first or last index in the data.
It would look something like this:
<Col className="floating-close layout-column">
<IconButton>
<i className="material-icons">close</i>
</IconButton>
{this.state.openIndex > 0 &&
<IconButton>
<i className="material-icons left">chevron_left</i>
</IconButton>}
{this.state.openIndex < approvalsData.length &&
<IconButton>
<i className="material-icons right">chevron_right</i>
</IconButton>}
</Col>

Resources