I'm trying to call an animation but simultaneously call an async function. Once that resolves it should stop the animation
console.log(context, "context value outside") //updating
const animate = useCallback(
async (index = 0) => {
console.log(context, "context value inside") //not updating
return setTimeout(() => {
// do animate
if (index === 3) {
animate(0)
} else {
animate(index + 1)
}
}, 800)
},
[context]
)
but when I call my trigger function that calls animate()
and getAsyncFunction()
it never updates the context value but I know the context value is updating because logging it in the main component body above renders a different value. why would it not update here? I even wrapped in useCallback to trigger it
I'm trying to call an animation but simultaneously call an async function. Once that resolves it should stop the animation
console.log(context, "context value outside") //updating
const animate = useCallback(
async (index = 0) => {
console.log(context, "context value inside") //not updating
return setTimeout(() => {
// do animate
if (index === 3) {
animate(0)
} else {
animate(index + 1)
}
}, 800)
},
[context]
)
but when I call my trigger function that calls animate()
and getAsyncFunction()
it never updates the context value but I know the context value is updating because logging it in the main component body above renders a different value. why would it not update here? I even wrapped in useCallback to trigger it
2 Answers
Reset to default 0- Issue seemed to be with the flow and logic of the code on how you start and stop the animation.
- If you're just running it on button click it is being handled by the useEffect you need to cleanup the timeout also.
function App() {
const [context, setContext] = useState(0); // Simulates the context value
const contextRef = useRef(context);
const animationRef = useRef(null); // Tracks the current animation timeout
// Update the ref whenever context changes
useEffect(() => {
contextRef.current = context;
}, [context]);
// Simulates an async function
const getAsyncFunction = async () => {
console.log("Async task started...");
return new Promise((resolve) => setTimeout(() => {
console.log("Async task completed!");
resolve();
}, 3000)); // 3 seconds delay
};
// Animation function
const animate = useCallback((index = 0) => {
console.log(contextRef.current, "context value inside animation"); // Always shows the latest context value
animationRef.current = setTimeout(() => {
if (index === 3) {
animate(0); // Loop back to start
} else {
animate(index + 1); // Increment index
}
}, 800); // Delay for animation
}, []);
// Trigger function
const trigger = useCallback(async () => {
console.log("Trigger called. Starting animation and async task...");
animate(); // Start animation
await getAsyncFunction(); // Wait for async task
if (animationRef.current) {
clearTimeout(animationRef.current); // Stop the animation
animationRef.current = null; // Clean up reference
}
console.log("Animation stopped after async task.");
}, [animate]);
return (
<div style={{ padding: "20px" }}>
<h1>React Animation with Async Example</h1>
<p>Current Context Value: {context}</p>
<button onClick={() => setContext((prev) => prev + 1)}>Update Context</button>
<button onClick={trigger}>Start Animation & Async Task</button>
</div>
);
}
export default App;
Please note that while a context object is specified as dependency for useCallback, it must specify the value of the context, not the context object as such. Please see below two sample codes, and see the difference.
a. When the context value is in the dependency array, then useCallback has returned the latest function definition on each change of the value.
b. When the context as such is in the dependency array, then useCallback does not return the latest definition.
Case a
App.js
import { createContext, useCallback, useContext, useEffect } from 'react';
import { useState } from 'react';
import { useRef } from 'react';
const someContext = createContext(null);
export default function App() {
const [someState, setsomeState] = useState(null);
useEffect(() => {
setTimeout(() => setsomeState(Math.random()), 1000);
}, [someState]);
return (
<>
<someContext.Provider value={someState}>
<Component />
</someContext.Provider>
<br />
<br />A new render by the someState changed to : {someState}
</>
);
}
function Component() {
let ref = useRef(null);
const contextValue = useContext(someContext);
const cachedFunction = useCallback(
() => console.log(`I am memoized with ${contextValue}`),
[contextValue]
);
useEffect(() => {
cachedFunction();
if (ref.current === cachedFunction) {
console.log('Memoized function retained');
} else {
console.log('Memoized function refreshed');
ref.current = cachedFunction; // setting the ref to the new function
}
});
return 'Check console';
}
Test run
Browser console logs as below :
// I am memoized with 0.3287557684453728
// Memoized function refreshed
// I am memoized with 0.08560524542093395
// Memoized function refreshed
// I am memoized with 0.664627542672463
// Memoized function refreshed
// I am memoized with 0.731629612163414
// Memoized function refreshed
// I am memoized with 0.7098038964262374
// Memoized function refreshed
Case b
App.js
...
const cachedFunction = useCallback(
() => console.log(`I am memoized with ${contextValue}`),
[someContext]
);
...
Test run
Browser console logs as below:
// I am memoized with null
// Memoized function refreshed
// I am memoized with null
// Memoized function retained
// I am memoized with null
// Memoized function retained
// I am memoized with null
// Memoized function retained
// I am memoized with null
// Memoized function retained
// I am memoized with null
// Memoized function retained
// I am memoized with null
await
on a promise-returning function. – trincot Commented Nov 19, 2024 at 13:48