Animated Values: useValue, useComputedValue & runTiming
Updating shape props from useState works, but every change re-renders the component. Skia has its own lightweight value system that updates the GPU surface directly, skipping React entirely, which is how you get smooth 60fps+ animation.
useValue
import { Canvas, Circle, useValue } from '@shopify/react-native-skia';
export function PulsingDot() {
const radius = useValue(30);
return (
<Canvas style={{ flex: 1 }}>
<Circle cx={100} cy={100} r={radius} color="#22d3ee" />
</Canvas>
);
}
radius is a mutable "Skia value" (a SkiaValue<number>), passing it straight into r tells Skia to re-read radius.current on every frame, changing radius.current = 50 updates the drawing immediately, with zero React re-render.
Animating with runTiming
import { runTiming, Easing } from '@shopify/react-native-skia';
runTiming(radius, 60, { duration: 500, easing: Easing.inOut(Easing.ease) });
runTiming(value, toValue, config) smoothly animates a Skia value from its current number to toValue over duration milliseconds, using the given easing curve, call it from a button press, a useEffect, or a gesture handler.
Deriving one value from another
useComputedValue recomputes automatically whenever a value it depends on changes, useful for keeping two animated properties in sync without duplicating the animation:
import { useComputedValue } from '@shopify/react-native-skia';
const opacity = useComputedValue(() => radius.current / 60, [radius]);
Here opacity always tracks radius, growing towards 1 as the circle grows towards its max radius, with no separate runTiming call needed for it.