← back to Ken

kalshi-dash/src/hooks/useAnimatedValue.js

48 lines

import { useState, useEffect, useRef } from 'react';

/**
 * useAnimatedValue — smoothly animate between number values using rAF
 *
 * @param {number} target   — the target value to animate toward
 * @param {number} duration — animation duration in ms (default: 800)
 * @returns {number}        — the current interpolated value (use in your render)
 *
 * Usage:
 *   const animatedBalance = useAnimatedValue(data?.balance ?? 0);
 *   // renders smooth transition whenever `data.balance` changes
 */
export default function useAnimatedValue(target, duration = 800) {
  const [current, setCurrent] = useState(target);
  const fromRef = useRef(target);
  const startRef = useRef(null);
  const rafRef = useRef(null);
  const targetRef = useRef(target);

  useEffect(() => {
    targetRef.current = target;
    fromRef.current = current;
    startRef.current = null;

    const animate = (ts) => {
      if (!startRef.current) startRef.current = ts;
      const elapsed = ts - startRef.current;
      const t = Math.min(1, elapsed / duration);

      // ease-out exponential
      const eased = t === 1 ? 1 : 1 - Math.pow(2, -10 * t);

      const next = fromRef.current + (targetRef.current - fromRef.current) * eased;
      setCurrent(next);

      if (t < 1) {
        rafRef.current = requestAnimationFrame(animate);
      }
    };

    rafRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(rafRef.current);
  }, [target]); // eslint-disable-line react-hooks/exhaustive-deps

  return current;
}