I'm trying to find a replacement for ComponentWillMount method with useEffect. But panresponder variable is not created before the initial render.
Can anyone please tell me if any mistakes I'm doing here? without useEffect, the page is getting rendered. gestures are not captured.
const makeup = () => {
useEffect(() => {
console.log('I am about to render!');
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: (e, gestureState) => true,
onPanResponderMove: (e, gestureState) => {....},
onPanResponderRelease: (e, gestaureState) => {....}
}));
},[]);
return ARTICLES.map((item, i) => {
console.log("Makeup i:"+i+" item:"+item);
if (i === index.currentIndex - 1) {
return (
<Animated.View
key={item.id}
style={swipeCardPosition.getLayout()}
{...panResponder.panHandlers}
>
</Animated.View>
);
}
if (i < index.currentIndex) {
return null;
}
return (
<Animated.View
key={item.id}
style={index.currentIndex === i ? position.getLayout() : null}
{...(index.currentIndex === i
? { ...panResponder.panHandlers }
: null)}
>
</Animated.View>
);
}).reverse();
};
[Error on execution][1]
[1]: https://i.stack.imgur.com/sls5E.png
Check the official docs. It describes how to use PanResponder with functional components.
For example:
const ExampleComponent = () => {
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponder: (e, gestureState) => true,
onPanResponderMove: (e, gestureState) => {....},
onPanResponderRelease: (e, gestaureState) => {....}
})
).current;
return <View {...panResponder.panHandlers} />;
};
Your example won't work, for a large number of reasons, most notably because you can't call useRef inside useEffect.
Related
im using PanResponder in React Native but i have a simple problem that i can't solve!!!
this is my code
class test extends React.Component {
constructor(props) {
super(props);
const position = new Animated.ValueXY();
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (evt, gs) => {
console.log(gs.dx);
position.setValue({ x: gs.dx , y: gs.dy })
},
onPanResponderRelease: (evt, gs) => {
},
});
this.state = { panResponder, position }
}
getCardStyle() {
const { position } = this.state;
return {
...position.getLayout(),
};
}
render() {
return (
<View>
{
this.props.cart.map((item, index) => {
return (
<Animated.View style={[this.getCardStyle() ,styles.CardContainer]} key={item.id}
{...this.state.panResponder.panHandlers}
>
<View><Image style={styles.image} source={{ uri: item.src}} /></View>
<Text style={styles.title}>{item.location}</Text>
</Animated.View>
)
})
}
</View>
);
}
}
i want to move every item finger
Separately but when i touch every item in list whole items start moving.
can anyone help me please??
SOLVE IT
I made another component named ListItem and in that component render FlatList items and it worked!
I need to read the value of useState in onPanResponderMove. On page load onPanResponderMove correctly logs the initial value of 0.
However after I click on TouchableOpacity to increment foo, the onPanResponderMove stills logs out 0 rather than it's new value.
export default function App() {
const [foo, setFoo] = React.useState(0);
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {},
onPanResponderMove: (evt, gestureState) => {
console.log(foo); // This is always 0
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {},
onPanResponderTerminate: (evt, gestureState) => {},
onShouldBlockNativeResponder: (evt, gestureState) => {
return true;
},
})
).current;
return (
<View style={{ paddingTop: 200 }}>
<TouchableOpacity onPress={() => setFoo(foo + 1)}>
<Text>Foo = {foo}</Text>
</TouchableOpacity>
<View
{...panResponder.panHandlers}
style={{ marginTop: 200, backgroundColor: "grey", padding: 100 }}
>
<Text>Text for pan responder</Text>
</View>
</View>
);
}
The pan responder depends on the value 'foo'. useRef wouldn't be a good choice here. You should replace it with useMemo
const panResponder = useMemo(
() => PanResponder.create({
[...]
onPanResponderMove: (evt, gestureState) => {
console.log(foo); // This is now updated
},
[...]
}),
[foo] // dependency list
);
Issue
The issue is that you create the PanResponder once with the foo which you have at that time. However with each setFoo call you'll receive a new foo from the useState hook. The PanResponder wont know that new foo. This happens due to how useRef works as it provides you a mutable object which lives through your whole component lifecycle. (This is explained in the react docs here)
(You can play around with the issue and a simple solution in this sandbox.)
Solution
In your case the simplest solution is to update the PanResponder function with the new foo you got from useState. In your example this would look like this:
export default function App() {
const [foo, setFoo] = React.useState(0);
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {},
onPanResponderMove: (evt, gestureState) => {
console.log(foo);
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {},
onPanResponderTerminate: (evt, gestureState) => {},
onShouldBlockNativeResponder: (evt, gestureState) => {
return true;
},
})
).current;
// update the onPanResponderMove with the new foo
panResponder.onPanResponderMove = (evt, gestureState) => {
console.log(foo);
},
return (
<View style={{ paddingTop: 200 }}>
<TouchableOpacity onPress={() => setFoo(foo + 1)}>
<Text>Foo = {foo}</Text>
</TouchableOpacity>
<View
{...panResponder.panHandlers}
style={{ marginTop: 200, backgroundColor: "grey", padding: 100 }}
>
<Text>Text for pan responder</Text>
</View>
</View>
);
}
Note
Always be extra careful if something mutable depends on your components state. If this is really necessary it is often a good idea to write a proper class or object with getters and setters. For example something like this:
const createPanResponder = (foo) => {
let _foo = foo;
const setFoo = foo => _foo = foo;
const getFoo = () => _foo;
return {
getFoo,
setFoo,
onPanResponderMove: (evt, gestureState) => {
console.log(getFoo());
},
...allYourOtherFunctions
}
}
const App = () => {
const [foo, setFoo] = React.useState(0);
const panResponder = useRef(createPanResponder(foo)).current;
panResponder.setFoo(foo);
return ( ... )
}
With useEffect you can create a new PanResponder when foo changes. However I'm not sure how performant this is.
export default function App() {
const [foo, setFoo] = React.useState(0);
const pan = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {},
onPanResponderMove: (evt, gestureState) => {
console.log(foo); // This is always 0
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {},
onPanResponderTerminate: (evt, gestureState) => {},
onShouldBlockNativeResponder: (evt, gestureState) => {
return true;
},
});
const [panResponder, setPanResponder] = React.useState(pan);
useEffect(() => {
setPanResponder(pan);
}, [foo]);
return (
<View style={{ paddingTop: 200 }}>
<TouchableOpacity onPress={() => setFoo(foo + 1)}>
<Text>Foo = {foo}</Text>
</TouchableOpacity>
<View
{...panResponder.panHandlers}
style={{ marginTop: 200, backgroundColor: "grey", padding: 100 }}
>
<Text>Text for pan responder</Text>
</View>
</View>
);
}
It looks like you're passing foo in an attempt to update the existing state. Instead, pass in the previous state and update it accordingly (functional update). Like this:
<TouchableOpacity onPress={() => setFoo(f => f + 1)}>
<Text>Foo = {foo}</Text>
</TouchableOpacity>
To make the current value of foo available inside the onPanResponderMove handler. Create a ref.
According to the docs:
The “ref” object is a generic container whose current property is mutable and can hold any value
So, with that in mind we could write:
const [foo, setFoo] = React.useState(0);
const fooRef = React.useRef()
React.useEffect(() => {
fooRef.current = foo
},[foo])
Then inside the onPanResponderMove handler you can access the ref's current value that we set before:
onPanResponderMove: (evt, gestureState) => {
console.log('fooRef', fooRef.current)
alert(JSON.stringify(fooRef))
},
Working example here
More info on stale state handling here
There are many examples available from great developers around the world for collapsible header which work based on the scroll value of scrollView. But I'm trying to implement the same by using PanResponder.
Here is what I'm trying to achieve imgur link
in terms of React Native if I explain the screen components we see in the image is a header at the top and a scrollView thereafter (lets skip the directory breadcrumbs)
initially when the screen renders the header stays collapsed, if we slide down the screen the header expands with a springy animation similarly when sliding up the header gets back to initial collapsed state with animation. During the header collapse / expansion the scrollView scroll stays lock until the header animation is complete i.e we have to track gesture via PanResponder.
If we slide up the header to any position that is less than 50% of the header then the header springs back & expands, similarly reverse happens when sliding down from a collapsed condition.
In last few days I went up to the expand & collapse part but can't figure out the springy effect for the header animation now my back is against the wall, any help will be much appreciated
Here is the code
class App extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
items: [1,2,3,4,5]
};
this.animY = new Animated.Value(0);
this.lastY = 0;
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
},
onPanResponderMove: (evt, gestureState) => {
let { dy } = gestureState;
this.animY.setValue(this.lastY + dy);
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
let { dy } = gestureState;
this.lastY += dy;
},
});
}
handleScroll = () => {
console.log('scroll init');
}
render() {
this.state.pan.addListener((value) => {
console.log('animY', this.animY);
});
const AnimateHeaderHeight = Animated.diffClamp(this.animY, -200, 0).interpolate(
{
inputRange: [ -200, 0 ],
outputRange: [ 50, 200 ],
extrapolate: 'clamp'
});
return (
<View style={{flex: 1}}>
<Animated.View style={[{backgroundColor: '#4287f5', height: AnimateHeaderHeight}]}>
<Text>Sample</Text>
</Animated.View>
<ScrollView style={{flex: 1, backgroundColor: '#e6efff'}}
onScroll={this.handleScroll}
{...this._panResponder.panHandlers}
scrollEnabled={false}
>
{this.state.items.map((item, index) => {
return (
<View style={styles.item} key={index}>
<Text>Sample</Text>
</View>
)
})}
</ScrollView>
</View>
)
}
};
here is the output of my above code
My project is a Animated view based on PanResponder.
The Animated.View has three child components.
When I dragged the animated view to top(and child component out of sight of the screen),the child content became shorter and shorter.
But no problem when dragged to bottom.
What is the matter,please?
Thanks!
=======
screen shot videos:
drag child: no problem
drag child: Problem occur when dragging
=====
source:
complete code:
drag panel
child component
main codes shown:
<View style={styles.draggableContainer}>
<Animated.View
onLayout={this.onLayout}
{...this.panResponder.panHandlers}
style={[
this.pan.getLayout(),
styles.aniView,
]}>
{this.renderChildren()}
</Animated.View>
</View>
renderChildren = () => {
const { source } = this.state;
const children = source.map((item, index) => {
return (
<View key={item.toString()}
style={{
width: WIDTH,
height: HEIGHT / 3,
flex: 1,
borderWidth: 1,
}}>
<ChildContent title={item} />
</View>
);
});
return children;
}
componentWillMount() {
this.pan.addListener((value) => {
this._value = value;
});
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => ((gestureState.dx != 0) && (gestureState.dy != 0)),
onPanResponderGrant: (e, gestureState) => {
this.pan.setOffset({ x: this._value.x, y: this._value.y });
this.pan.setValue({ x: 0, y: 0 });
},
onPanResponderMove: Animated.event([null, {
dx: this.pan.x,
dy: this.pan.y,
}]),
onPanResponderRelease: (e, gesture) => {
this.pan.flattenOffset();
this.animatePanel();
}
});
}
Environment:
OS: macOS High Sierra 10.13.6
I got the solution:
update to react-native 0.56.
(using react-native-git-upgrade ,and update babel-preset-react-native to 5.0.2).
then the problem disappeared.
I have implemented PanResponder in my project but it only works when I touch non touchable elements . When I touch touchable elements like TouchableOpacity, PanReponder does not responds.But when I move my finger on TouchableOpacity PanResponder responds.
Same thing happening for Button also
Please tell me what might be the problem.
Expo Link : https://snack.expo.io/SyYrtq87W
import React, { Component } from 'react';
import { Button, PanResponder, View, StyleSheet,TouchableOpacity } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
state = {
show : false
};
_panResponder = {};
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => {
alert('clicked')
console.log('clicked')
return true
},
onMoveShouldSetPanResponder: () => {
alert('moved')
console.log('moved')
return true
},
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderTerminationRequest: () => true,
onShouldBlockNativeResponder: () => false,
});
}
render() {
return (
<View
style={styles.container}
collapsable={false}
{...this._panResponder.panHandlers}>
{/*********************PanResponder does not respond***************************/}
<TouchableOpacity>
<View style={{width:200, height:200,backgroundColor:'red'}}>
</View>
</TouchableOpacity>
<Button
title="Here is a button for some reason"
onPress={() => {}}
/>
{/*****************************************************************************/}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
}
});
I had a similar issue. Basically because you always return true in onStartShouldSetPanResponder and/or onMoveShouldSetPanResponder they will take command over that touch event.
My workaround: Don't consider the touch "yours" (PanResponder's) unless the user moved it a little first by a threshold you set.
const touchThreshold = 20;
const panResponder = PanResponder.create({
onStartShouldSetPanResponder : () => false,
onMoveShouldSetPanResponder : (e, gestureState) => {
const {dx, dy} = gestureState;
return (Math.abs(dx) > touchThreshold) || (Math.abs(dy) > touchThreshold);
},
...
});
Finally It hit me. It was a very silly mistake.
forgot to add alert('clicked') at
onStartShouldSetPanResponderCapture: ()
Now,
onStartShouldSetPanResponderCapture: () => {alert('clicked') ; return false},
Expo Link : https://snack.expo.io/ryWx7lBrb
Now, it takes touch everywhere, including Touchables and Buttons.
import React, { Component } from 'react';
import { Button, PanResponder, View, StyleSheet,TouchableOpacity } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
state = {
show : false
};
_panResponder = {};
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => {
alert('clicked')
return true
},
onMoveShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => {alert('clicked') ; return false},
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderTerminationRequest: () => true,
onShouldBlockNativeResponder: () => false,
});
}
render() {
return (
<View
style={styles.container}
collapsable={false}
{...this._panResponder.panHandlers}>
{/*********************PanResponder now responds***************************/}
<TouchableOpacity>
<View style={{width:200, height:200,backgroundColor:'red'}}>
</View>
</TouchableOpacity>
<Button
title="Here is a button for some reason"
onPress={() => {}}
/>
{/*****************************************************************************/}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
}
});
Touchables will not work if you set this
onStartShouldSetPanResponder: () => true
This will entirely take control over the touches. It is always a good option to take control only if the user starts dragging the component.
onMoveShouldSetPanResponder: () => true
Here is an example for PanResponder.
this.panResponder = PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: (event, gesture) => {
if (gesture.dy > 0 && this.state.size._value < width) {
this.state.size.setValue(40 + gesture.dy)
}
},
onPanResponderRelease: () => {
Animated.spring(this.state.size, {
toValue: 40
}).start()
}
})
This is my JSX code.
<Animated.View {...this.panResponder.panHandlers}>
<TouchableWithoutFeedback onPress={this.openImage}>
<Animated.Image style={profileImage}
source={{uri: 'https://example.com/face.jpg'}}/>
</TouchableWithoutFeedback>
</Animated.View>