Skip to main content
React Native Skia provides several hooks and utilities to simplify common animation patterns and workflows.

useValue

Deprecated: Use Reanimated’s useSharedValue instead.
The legacy useValue hook is deprecated in favor of Reanimated 3’s useSharedValue for better performance and features.
// Old (deprecated)
import { useValue } from "@shopify/react-native-skia";
const x = useValue(0);

// New (recommended)
import { useSharedValue } from "react-native-reanimated";
const x = useSharedValue(0);

useComputedValue

Deprecated: Use Reanimated’s useDerivedValue instead.
The legacy useComputedValue hook is deprecated in favor of Reanimated 3’s useDerivedValue.
// Old (deprecated)
import { useComputedValue } from "@shopify/react-native-skia";
const doubled = useComputedValue(() => x.current * 2, [x]);

// New (recommended)
import { useDerivedValue } from "react-native-reanimated";
const doubled = useDerivedValue(() => x.value * 2);

useLoop

Create looping animations with Reanimated:
import { useSharedValue, withRepeat, withTiming, Easing } from "react-native-reanimated";
import { useEffect } from "react";

const useLoop = (duration: number = 1000) => {
  const progress = useSharedValue(0);

  useEffect(() => {
    progress.value = withRepeat(
      withTiming(1, { duration, easing: Easing.linear }),
      -1,  // infinite
      false // don't reverse
    );
  }, [duration]);

  return progress;
};

// Usage
const LoopDemo = () => {
  const progress = useLoop(2000);

  const rotation = useDerivedValue(() => {
    return progress.value * Math.PI * 2;
  });

  return (
    <Canvas style={{ flex: 1 }}>
      <Group transform={[{ rotate: rotation }]}>
        <Rect x={108} y={108} width={40} height={40} color="blue" />
      </Group>
    </Canvas>
  );
};

useTiming

Simplify timing animations:
import { useSharedValue, withTiming } from "react-native-reanimated";
import { useEffect } from "react";

const useTiming = (
  toValue: number,
  duration: number = 300,
  delay: number = 0
) => {
  const value = useSharedValue(0);

  useEffect(() => {
    const timer = setTimeout(() => {
      value.value = withTiming(toValue, { duration });
    }, delay);

    return () => clearTimeout(timer);
  }, [toValue, duration, delay]);

  return value;
};

// Usage
const TimingDemo = () => {
  const opacity = useTiming(1, 500);

  return (
    <Canvas style={{ flex: 1 }}>
      <Circle cx={128} cy={128} r={50} color="red" opacity={opacity} />
    </Canvas>
  );
};

useSpring

Simplify spring animations:
import { useSharedValue, withSpring } from "react-native-reanimated";
import { useEffect } from "react";

const useSpring = (
  toValue: number,
  config?: { damping?: number; stiffness?: number }
) => {
  const value = useSharedValue(0);

  useEffect(() => {
    value.value = withSpring(toValue, config);
  }, [toValue]);

  return value;
};

// Usage
const SpringDemo = () => {
  const scale = useSpring(1.5, { damping: 10, stiffness: 100 });

  return (
    <Canvas style={{ flex: 1 }}>
      <Group transform={[{ scale }]}>
        <Circle cx={128} cy={128} r={50} color="green" />
      </Group>
    </Canvas>
  );
};

useAnimatedValue

Manage animated values with controls:
import { useSharedValue, withTiming, cancelAnimation } from "react-native-reanimated";
import { useCallback } from "react";

const useAnimatedValue = (initialValue: number = 0) => {
  const value = useSharedValue(initialValue);

  const animateTo = useCallback((toValue: number, duration: number = 300) => {
    value.value = withTiming(toValue, { duration });
  }, []);

  const reset = useCallback(() => {
    value.value = withTiming(initialValue);
  }, [initialValue]);

  const stop = useCallback(() => {
    cancelAnimation(value);
  }, []);

  return { value, animateTo, reset, stop };
};

// Usage
const AnimatedValueDemo = () => {
  const { value, animateTo, reset } = useAnimatedValue(0);

  return (
    <View>
      <Canvas style={{ width: 256, height: 256 }}>
        <Circle cx={value} cy={128} r={20} color="blue" />
      </Canvas>
      <Button title="Animate" onPress={() => animateTo(200)} />
      <Button title="Reset" onPress={reset} />
    </View>
  );
};

useAnimatedPath

Animate between paths:
import { useDerivedValue, useSharedValue } from "react-native-reanimated";
import { Skia, SkPath } from "@shopify/react-native-skia";

const useAnimatedPath = (from: SkPath, to: SkPath) => {
  const progress = useSharedValue(0);

  const path = useDerivedValue(() => {
    return from.interpolate(to, progress.value)!;
  });

  const animateTo = useCallback((target: number, duration: number = 300) => {
    progress.value = withTiming(target, { duration });
  }, []);

  return { path, animateTo, progress };
};

// Usage
const PathMorphDemo = () => {
  const circle = Skia.Path.Make();
  circle.addCircle(128, 128, 64);

  const rect = Skia.Path.Make();
  rect.addRect(Skia.XYWHRect(64, 64, 128, 128));

  const { path, animateTo } = useAnimatedPath(circle, rect);

  return (
    <View>
      <Canvas style={{ width: 256, height: 256 }}>
        <Path path={path} color="purple" />
      </Canvas>
      <Button title="To Rect" onPress={() => animateTo(1)} />
      <Button title="To Circle" onPress={() => animateTo(0)} />
    </View>
  );
};

useClockValue

Create time-based animations:
import { useFrameCallback, useSharedValue } from "react-native-reanimated";

const useClockValue = () => {
  const time = useSharedValue(0);

  useFrameCallback((frameInfo) => {
    time.value = frameInfo.timestamp / 1000; // Convert to seconds
  });

  return time;
};

// Usage
const ClockDemo = () => {
  const time = useClockValue();

  const x = useDerivedValue(() => {
    return 128 + Math.cos(time.value * 2) * 50;
  });

  const y = useDerivedValue(() => {
    return 128 + Math.sin(time.value * 2) * 50;
  });

  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Circle cx={x} cy={y} r={20} color="red" />
    </Canvas>
  );
};

useAnimatedSwitch

Toggle between two values:
import { useSharedValue, withTiming } from "react-native-reanimated";
import { useState, useEffect } from "react";

const useAnimatedSwitch = (
  offValue: number,
  onValue: number,
  initialState: boolean = false
) => {
  const [isOn, setIsOn] = useState(initialState);
  const value = useSharedValue(initialState ? onValue : offValue);

  useEffect(() => {
    value.value = withTiming(isOn ? onValue : offValue);
  }, [isOn, offValue, onValue]);

  const toggle = useCallback(() => {
    setIsOn((prev) => !prev);
  }, []);

  return { value, isOn, toggle, setIsOn };
};

// Usage
const SwitchDemo = () => {
  const { value, toggle } = useAnimatedSwitch(0, 1);

  return (
    <View>
      <Canvas style={{ width: 256, height: 256 }}>
        <Circle cx={128} cy={128} r={50} color="blue" opacity={value} />
      </Canvas>
      <Button title="Toggle" onPress={toggle} />
    </View>
  );
};

useSequence

Chain animations in sequence:
import { useSharedValue, withSequence, withTiming } from "react-native-reanimated";
import { useEffect } from "react";

const useSequence = (values: number[], duration: number = 300) => {
  const value = useSharedValue(values[0] || 0);

  useEffect(() => {
    const animations = values.map((v) => withTiming(v, { duration }));
    value.value = withSequence(...animations);
  }, [values, duration]);

  return value;
};

// Usage
const SequenceDemo = () => {
  const scale = useSequence([1, 1.5, 1.2, 1], 200);

  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Group transform={[{ scale }]}>
        <Circle cx={128} cy={128} r={50} color="orange" />
      </Group>
    </Canvas>
  );
};

useGesturePosition

Track gesture position:
import { useSharedValue } from "react-native-reanimated";
import { Gesture } from "react-native-gesture-handler";

const useGesturePosition = (initialX: number = 0, initialY: number = 0) => {
  const x = useSharedValue(initialX);
  const y = useSharedValue(initialY);

  const gesture = Gesture.Pan()
    .onChange((e) => {
      x.value += e.changeX;
      y.value += e.changeY;
    });

  return { x, y, gesture };
};

// Usage
const GesturePositionDemo = () => {
  const { x, y, gesture } = useGesturePosition(128, 128);

  return (
    <GestureDetector gesture={gesture}>
      <Canvas style={{ width: 256, height: 256 }}>
        <Circle cx={x} cy={y} r={30} color="cyan" />
      </Canvas>
    </GestureDetector>
  );
};

Cleanup Animations

Always clean up animations on unmount:
import { useEffect } from "react";
import { useSharedValue, withRepeat, withTiming, cancelAnimation } from "react-native-reanimated";

const CleanupDemo = () => {
  const rotation = useSharedValue(0);

  useEffect(() => {
    rotation.value = withRepeat(
      withTiming(2 * Math.PI, { duration: 2000 }),
      -1,
      false
    );

    // Cleanup on unmount
    return () => {
      cancelAnimation(rotation);
    };
  }, []);

  return (
    <Canvas style={{ flex: 1 }}>
      <Group transform={[{ rotate: rotation }]}>
        <Rect x={108} y={108} width={40} height={40} color="red" />
      </Group>
    </Canvas>
  );
};

Best Practices

  • Always use useSharedValue for animation values
  • Use useDerivedValue for computed values
  • Clean up animations on component unmount
  • Avoid creating new objects in derived values
  • Use cancelAnimation to stop running animations
  • Leverage useFrameCallback for frame-based animations
  • Keep worklets pure and avoid side effects

Build docs developers (and LLMs) love