I am trying to enable panResponder to move a component through the screen in a drag and drop mode. However this drag and drop must be initiated through a longPress on such element.
longPress captures the event and so when panResponder is enabled onStartShouldSetPanResponder => this.state.panEnabled we need to press again.
I would like to fire a native event, or, else, activate the drag without pressing again. Is it possible to re-emit a native event? Can we pass it to the panResponder in any way?
Thanks!
Ive got this working without using longTouch and instead just using the panResponder (same effect as the longTouch though), take a look:
import React, { Component } from 'react';
import {
View,
PanResponder
} from 'react-native';
const LONGPRESS_TIMER = 300;
class DraggableComponent extends Component {
constructor() {
super();
this.timer = null;
this.state = {
canMove: false
}
this._panResponder = this.setUpPanResponder();
}
setUpPanResponder() {
const self = this;
return PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
self.timer = setTimeout(() => {
self.setState({canMove: true});
}, LONGPRESS_TIMER);
},
onPanResponderMove: (evt, gestureState) => {
if(this.state.canMove) {
console.log('moving', gestureState);
// code to move view here
}
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
clearTimeout(self.timer);
self.setState({canMove: false});
}
});
}
render() {
return <View
style={{
width: 100,
height: 100,
backgroundColor: this.state.canMove ? 'green' : 'blue'}}
{...this._panResponder.panHandlers} />
}
}
You can adjust the time it takes to trigger the "canMove" state by changing the LONGPRESS_TIMER const, Hope it helps!
Related
React Native documents give an example using PanResponder where a property is set using "._value".
What does "._value" do?
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value, // What do you do?
y: pan.y._value // You too!
});
},
onPanResponderMove: Animated.event(
[
null,
{
dx: pan.x,
dy: pan.y
}
]
),
onPanResponderRelease: () => {
pan.flattenOffset();
}
})
).current;
I am trying to show a toast notification when a service worker updates but the toast notification shows up two times. It seems that its because of the useEffect hook but am not being able to understand how to fix the problem.
In App.js
import React, { Fragment, useState, useEffect } from "react";
import * as serviceWorker from "../serviceWorker";
import { useSnackbar } from "notistack";
const App = () => {
const [newVersionAvailable, setNewVersionAvailable] = useState(false);
const [waitingWorker, setWaitingWorker] = useState({});
const { enqueueSnackbar } = useSnackbar();
const onServiceWorkerUpdate = (registration) => {
setNewVersionAvailable(true);
setWaitingWorker(registration && registration.waiting);
}
const updateServiceWorker = () => {
if(waitingWorker) {
waitingWorker.addEventListener("statechange", event => {
if(event.target.state === "activated") {
setNewVersionAvailable(false);
window.location.reload();
}
})
waitingWorker.postMessage({ type: "SKIP_WAITING" });
}
}
const updateAction = () => {
return (
<Fragment>
<Button
className="snackbar-button"
size="small"
onClick={ updateServiceWorker }
>
{ "Update" }
</Button>
</Fragment>
)
}
useEffect(() => {
serviceWorker.register({ onUpdate: onServiceWorkerUpdate });
if(newVersionAvailable) {
enqueueSnackbar("New Update Available!", {
persist: true,
anchorOrigin: {
vertical: "bottom",
horizontal: "center",
},
action: updateAction,
})
}
})
return (...);
Add newVersionAvailable to the effect's dependency array, then set back to false once the toast it fired off so it isn't triggered again.
useEffect(() => {
serviceWorker.register({ onUpdate: onServiceWorkerUpdate });
if(newVersionAvailable) {
enqueueSnackbar("New Update Available!", {
persist: true,
anchorOrigin: {
vertical: "bottom",
horizontal: "center",
},
action: updateAction,
});
setNewVersionAvailable(false);
}
}, [newVersionAvailable])
Hard to know from the code you've posted but what (if any) props do you have ? As it stands your useEffect will respond to every prop change. Perhaps you need to add a dependency array and only have useEffect trigger on certain prop changes.
useEffect(() => {
serviceWorker.register({ onUpdate: onServiceWorkerUpdate });
if(newVersionAvailable) {
enqueueSnackbar("New Update Available!", {
persist: true,
anchorOrigin: {
vertical: "bottom",
horizontal: "center",
},
action: updateAction,
})
}
}, [prop_whose_change_you_want_to_trigger_this_useEffect])
First off, I'm new to asking stack overflow questions, so go easy on me guys.
I am trying to figure out how to detect collision of components; namely, a draggable component containing a button-circle component. Sadly, there appears to be zero information online regarding this. I'm thinking the draggable component may be able to handle some sort of radius tracking, which is computed when the user takes their finger off the screen, but hard tracking the radius would seem to be very resource intensive. I really need some ideas here guys. Relevant code is below.
Draggable.js
import React, { Component } from "react";
import {
PanResponder,
Animated,
} from "react-native";
import CircleButton from 'react-native-circle-button';
export default class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
};
}
//Function to handle animations and button pressing of bubbles.
componentWillMount() {
let finalx = 0;
let finaly = 0;
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
onMoveShouldSetPanResponder: (evt, gestureState) => {
return gestureState.dx <= finalx && gestureState.dy <= finaly;
},
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
return gestureState.dx <= finalx && gestureState.dy <= finaly;
},
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setValue({x: 0, y: 0});
this.finalx = gestureState.dx;
this.finaly = gestureState.dy;
Animated.spring(
this.state.scale,
{ toValue: 1.1, friction: 3 }
).start();
},
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
Animated.spring(
this.state.pan,
{ toValue: {x:0, y:0}},
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
}
});
}
render() {
let { pan } = this.state;
let rotate = '0deg';
let [translateX, translateY] = [pan.x, pan.y];
let moveableStyle = {transform: [{translateX}, {translateY}, {rotate}]};
const panStyle = {
transform: this.state.pan.getTranslateTransform()
}
return (
<Animated.View
{...this._panResponder.panHandlers}
style={[moveableStyle, panStyle]}>
<CircleButton />
</Animated.View>
);
}
}
Homepage.js
<View style={styles.middleContainer}>
{ this.state.bubbles.map((item, key)=>(
<Draggable key={key}> { item }</Draggable>)
)}
</View>
I have a webview inside one of the screens of tab navigator. There are elements inside the webview which the user could swipe horizontally to show more information. The problem I have is that whenever the user tries to swipe horizontally inside the webview, the tab navigator detects the swipe instead and this results in the tab changing. This only happens on Android devices but not iOS devices.
To solve this issue, I am trying to set PanResponder inside the webview but it's not working. The related methods are not even called because the console logs are not printed when I am dragging my finger across the webview. Below is my code for the webview:
import React, { Component } from 'react';
import { WebView, View, PanResponder} from 'react-native';
class CustomWebPage extends Component {
componentWillMount() {
this.gesture = PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
console.log('onPanResponderGrant');
},
onPanResponderMove: (evt, gestureState) => {
console.log('onPanResponderMove: ', evt, ', ', gestureState);
},
onPanResponderTerminate: (evt, gestureState) => {
console.log('onPanResponderTerminate: ', evt, ', ', gestureState);
},
onPanResponderRelease: (evt, gestureState) => {
console.log('onPanResponderRelease: ', evt, ', ', gestureState);
},
});
}
render() {
return (
<WebView
source={{ uri: 'https://www.google.com.sg/' }}
style={{ flex: 1 }}
{...this.gesture.panHandlers}
/>
);
}
}
export default CustomWebPage;
Replace componentWillMount() with constructor()
so here,
constructor(props) {
super(props) {
// Here your PanResponder related codes
}
Using react-native, I'm creating sub-Components within the parent App and providing their position to the array this.state.objLocation within the parent App.
I can get the initial location data into the array straight after the render, but because my subcomponents are draggable, each time they re-render on drag, it adds a new position object to the array.
I'd like to avoid this, and I thought that creating this.state = { firstRender: true } in the constructor and then using componentDidMount = () => { this.setState({ firstRender: false }) } after the first render would allow me to create a 'gate' to stop the addition of the extra position objects.
I can see that if I comment out //componentDidMount = () => { this.setState({ firstRender: false }) } then I will get multiple entries to my array but if it's included in the class I get absolutely none.
So possibly my interpretation of the render lifecycle and componentDidMount is incorrect?
Here is my code.
// App
import React, { Component } from 'react';
import { View, Text, } from 'react-native';
import styles from './cust/styles';
import Draggable from './cust/draggable';
const dataArray = [{num: 1,id: 'A',},{num: 2,id: 'B',},{num: 3,id: 'Z',}]
export default class Viewport extends Component {
constructor(props){
super(props);
this.state = {
dID : null,
objLocation: [],
firstRender: true,
};
}
render(){
return (
<View style={styles.mainContainer}>
<View style={styles.draggableContainer}>
<Text>Draggable Container</Text> {dataArray.map( d => { return(
<Draggable
id={d.id}
onLayout={ e=> this.onLayout(e)}
onPanResponderGrant={(dID) =>this.setState({ dID })}
onPanResponderRelease={() => this.setState({dID: null})} /> ) })}
<View style={[styles.findPoint ]} />
</View>
<View style={styles.infoBar}>
<Text>{this.state.dID ? this.state.dID : ''}</Text>{this.compFrame()}
</View>
</View>
);
}
onLayout = (e) => {
if ( e && this.state.firstRender) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
objLocation: this.state.objLocation.concat([position])
});
}
}
componentWillMount = () => {
console.log("START");
}
compFrame = () => {
return(
this.state.objLocation.map( d => {<View style={[styles.findPoint2,{left: d.x, top: d.y, width: d.width, height: d.height} ]} ></View>})
)
}
componentDidMount = () => {
this.setState({firstRender: true })
console.log(this.state.objLocation.length);
}
}
// Draggable
import React, { Component } from 'react';
import { Text, PanResponder, Animated } from 'react-native';
import styles from './styles';
class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.props.onPanResponderGrant(this.props.id);
},
onPanResponderMove: Animated.event([ null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
},
]),
onPanResponderRelease: () => {
Animated.spring(this.state.pan, { toValue: { x: 0, y: 0 } }).start();
this.props.onPanResponderRelease();
},
});
}
render() {
return (
<Animated.View
onLayout={ (e) => this.props.onLayout(e) }
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), styles.circleAlt, styles.position]}>
<Text style={styles.textAlt}>Drag me!</Text>
<Text style={styles.textNum}>{this.props.id}</Text>
</Animated.View>
);
}
componentDidMount = () => {
this.props.onLayout(this.props.dragEvent)
}
}
export default Draggable;
// Output of console.log
START xxx
0
{width:108,height:108,x:133.5,y:376.5}
{width:108,height:108,x:133.5,y:78.5}
{width:108,height:108,x:133.5,y:227.5}
You could set the firstRender state in onLayout function
onLayout = (e) => {
if ( e && this.state.firstRender) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
firstRender: false,
objLocation: this.state.objLocation.concat([position])
});
}
}
According to the information provided by you, your onLayout function is called by the component so its not included in the component lifecycle process, so when the component completes its lifecycle it goes into componentDidMount after mounting (which is not calling onLayout func) & thus changed the firstRender state to false and hence when you drag the component each time it goes from true to false.
I hope this explains
I feel like I've hacked this, to get it to work, so please correct me as to correct procedure.
This is the onLayout method from the App. I've included an if statement that checks if the new positions array length is equal too the dataArray length that the draggable items are based on.
It looks like this.
onLayout = (e) => {
if ( this.state.objLocation.length != dataArray.length ) {
if ( e ) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
objLocation: this.state.objLocation.concat([position])
});
}
}
}