Reputation: 28830
I have this hook which is to abstract requestAnimationFrame
:
export const useAnimationIncrease = ({
easingName = EasingName.linear,
diff: end,
duration = 500
}: AnimationIncreaseProps) => {
const [increase, setIncrease] = useState(0);
const start = useRef<any>(null);
useLayoutEffect(() => {
let raf: number;
const frame = (timestamp: number) => {
if (!start.current) {
start.current = timestamp;
}
const time = timestamp - start.current;
// get percent of completion in range [0, 1]
const percentRange = Math.min(time / duration, 1);
const percent = easing[easingName as string](percentRange);
setIncrease(increase + end * percent);
if (time < duration) {
raf = requestAnimationFrame(frame);
}
};
raf = requestAnimationFrame(frame);
return () => {
cancelAnimationFrame(raf);
};
}, [end]);
return increase;
};
it used like this:
const increase = useAnimationIncrease({ easingName: EasingName.inOutCubic, diff: 10 });
It will return the increase each time so I can use it for scrolling etc.
How would I trigger this in a button click though or am I approaching this wrong.
If I try this;
onClick={(e) => {
const increase = useAnimationIncrease({ easingName: EasingName.inOutCubic, diff: 10 });
// use increase
}
Then I get the error message:
Hooks can only be called inside the body of a function component.
Upvotes: 5
Views: 857
Reputation: 281834
According to the rules of hooks
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)
And since you are violating that rule by calling hooks in onClick
event handler, you are getting this warning
As a workaround you can set a variable which you can use to trigger the animation
export const useAnimationIncrease = ({
easingName = EasingName.linear,
diff: end,
duration = 500
}: AnimationIncreaseProps) => {
const [increase, setIncrease] = useState(0);
const [startAnimation, setStartAnimation] = useState(0);
const start = useRef<any>(null);
useLayoutEffect(() => {
let raf: number;
const frame = (timestamp: number) => {
if (!start.current) {
start.current = timestamp;
}
const time = timestamp - start.current;
// get percent of completion in range [0, 1]
const percentRange = Math.min(time / duration, 1);
const percent = easing[easingName as string](percentRange);
setIncrease(increase + end * percent);
if (time < duration) {
raf = requestAnimationFrame(frame);
}
};
raf = requestAnimationFrame(frame);
return () => {
cancelAnimationFrame(raf);
};
},[end, startAnimation]);
return [increase, startAnimation, setStartAnimation];
};
and use it like
const [increase, startAnimation, setStartAnimation] = useAnimationIncrease({ easingName: EasingName.inOutCubic, diff: 10 });
onClick={(e) => {
setStartAnimation(prev => prev + 1);
// use increase now
}
Upvotes: 3