I am getting different behavior between the two code blocks when I believe they should be the same. My goal is to update compass heading using watchHeadingAsync
and perform and animation from the previous heading to the new heading.
The first code block results in heading[0]
always being 0
and heading[1]
being the current heading. The second block performs as expected. Is there some race condition I am unaware of that is causing this? Any insight would be greatly appreciated!
const [heading, setHeading] = useState([0,0]);
useEffect(() => {
Location.watchHeadingAsync((headObj) => {
let h = Math.round(headObj.magHeading) * -1;
setHeading([heading[1], h]);
});
}, []);
console.log(heading[0], '->', heading[1]);
const [heading, setHeading] = useState([0,0]);
const oldHeading = useRef(0);
const newHeading = useRef(0);
useEffect(() => {
Location.watchHeadingAsync((headObj) => {
let h = Math.round(headObj.magHeading) * -1;
oldHeading.current = newHeading.current;
newHeading.current = h;
setHeading([oldHeading.current, newHeading.current]);
});
}, []);
console.log(heading[0], '->', heading[1]);
3
Answers
The
heading
variable inside theuseEffect
callback is the constant value from when the effect ran. Indeed, it’s always[0, 0]
and never changes. To fix this stale closure problem, you want to use the updater callback variant of the state setter:Do not use refs as a workaround for this.
The reason you’re observing different behavior between the two code blocks is indeed related to a race condition.
In this block,
you’re setting the heading state based on the current value of heading, but since setHeading is asynchronous, it won’t immediately update the state.
in the second block, using useRef ensures that you capture the correct previous heading value before updating the state, thus producing the expected behavior.
The difference in behavior between the two code blocks stems from how you’re managing state updates in the
useEffect
hook. Let’s break down the issue:Code Block 1:
heading
as an array to store both the old and new headings.useEffect
, you update the state usingsetHeading([heading[1], h])
. This creates a new array with the previous heading at index 1 and the new heading at index 0.setHeading
, the state might not be updated immediately. In the next render, you log theheading
state, which might still have the old values. This explains whyheading[0]
is always 0.Code Block 2:
oldHeading
andnewHeading
.useEffect
, you updateoldHeading.current
andnewHeading.current
directly. These are ref values, which hold a mutable reference to a value. So, the changes are reflected immediately.heading
state usingsetHeading([oldHeading.current, newHeading.current])
. This ensures you’re setting the state with the latest values from the refs.I don’t know whether the issue in the first block is technically a race condition or not, but it is an asynchronous update behavior of React’s state management (useState()).