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 ( ... )
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With