Migrating to the Async API (v0.3.2+)
This release introduces an async-first API to prepare for the new experimental Rive runtime. Synchronous methods that block the JS thread are deprecated and replaced with async equivalents. State machine input and text run methods are deprecated in favor of data binding and will be removed entirely in the experimental runtime (and therefore in upcoming @rive-app/react-native versions).
What’s Changed
- Async methods replace all synchronous ViewModel and property accessors
- Name-based access replaces count/index-based ViewModel and artboard lookups
getValueAsync() / set() replace property.value for reading and writing properties
useRiveNumber and similar hooks now start as undefined until the first listener emission
- State machine inputs, text runs, and events are deprecated and will be removed in the experimental runtime — use data binding instead
Migration Steps
1. ViewModel Access
const names = await file.getViewModelNamesAsync();
const vm = await file.viewModelByNameAsync('Person');
const defaultVM = await file.defaultArtboardViewModelAsync();
const count = file.viewModelCount;
const vm = file.viewModelByName('Person');
const defaultVM = file.defaultArtboardViewModel();
2. Instance Creation
const instance = await vm.createDefaultInstanceAsync();
const named = await vm.createInstanceByNameAsync('player1');
const blank = await vm.createBlankInstanceAsync();
const instance = vm.createDefaultInstance();
const named = vm.createInstanceByName('player1');
const blank = vm.createInstance();
The useViewModelInstance hook handles ViewModel resolution and instance
creation for you — in most cases you don’t need to call these methods
directly.
3. Property Value Access
const num = await prop.getValueAsync();
prop.set(42);
const num = prop.value;
prop.value = 42;
4. Nested ViewModelInstance Access
const nested = await instance.viewModelAsync('Header');
const nested = instance.viewModel('Header');
5. List Property Access
const len = await listProp.getLengthAsync();
const item = await listProp.getInstanceAtAsync(0);
const len = listProp.length;
const item = listProp.getInstanceAt(0);
6. Artboard Access
const count = await file.getArtboardCountAsync();
const names = await file.getArtboardNamesAsync();
const count = file.artboardCount;
const names = file.artboardNames;
7. Hook Value Guarding
Property hooks (useRiveNumber, useRiveString, useRiveBoolean, useRiveColor, useRiveEnum) now return undefined as their initial value until the first listener emission.
const { value: count, setValue: setCount } = useRiveNumber(
'Counter/Value',
instance
);
// Guard for undefined in render
<Text>{count !== undefined ? count.toFixed(2) : '...'}</Text>
// Guard in updater functions
setCount((prev) => (prev ?? 0) + 1);
const { value: count, setValue: setCount } = useRiveNumber(
'Counter/Value',
instance
);
// Previously available synchronously on first render
<Text>{count.toFixed(2)}</Text>
8. Async Setup Pattern
Synchronous useMemo chains for ViewModel setup should be replaced with useState + useEffect, or simplified with the useViewModelInstance hook.
const { riveFile } = useRiveFile(require('./animation.riv'));
const instance = useViewModelInstance(riveFile);
const { value: health, setValue: setHealth } = useRiveNumber(
'health',
instance
);
if (!instance) return <ActivityIndicator />;
return <RiveView file={riveFile} dataBind={instance} />;
const { riveFile } = useRiveFile(require('./animation.riv'));
const [instance, setInstance] = useState<ViewModelInstance>();
useEffect(() => {
if (!riveFile) return;
let cancelled = false;
(async () => {
const vm = await riveFile.defaultArtboardViewModelAsync();
const vmi = await vm?.createDefaultInstanceAsync();
if (!cancelled) setInstance(vmi ?? undefined);
})();
return () => { cancelled = true; };
}, [riveFile]);
function Setup({ file }) {
const vm = useMemo(() => file.defaultArtboardViewModel(), [file]);
const instance = useMemo(
() => vm?.createDefaultInstance(),
[vm]
);
if (!instance) return null;
return <MyComponent instance={instance} file={file} />;
}
Quick Reference
| Deprecated | Replacement |
|---|
file.viewModelByName(name) | await file.viewModelByNameAsync(name) |
file.defaultArtboardViewModel() | await file.defaultArtboardViewModelAsync() |
file.viewModelCount | (await file.getViewModelNamesAsync()).length |
file.viewModelByIndex(i) | await file.viewModelByNameAsync(name) |
file.artboardCount / artboardNames | await file.getArtboardCountAsync() / getArtboardNamesAsync() |
vm.createDefaultInstance() | await vm.createDefaultInstanceAsync() |
vm.createInstanceByName(name) | await vm.createInstanceByNameAsync(name) |
vm.createInstance() | await vm.createBlankInstanceAsync() |
instance.viewModel(path) | await instance.viewModelAsync(path) |
listProp.length | await listProp.getLengthAsync() |
listProp.getInstanceAt(i) | await listProp.getInstanceAtAsync(i) |
prop.value (read) | await prop.getValueAsync() |
prop.value = x (write) | prop.set(x) |
| Limitation | Details |
|---|
replaceViewModel() | No-op on Android. Works on iOS. |
addListener() on image/list properties | No-op on both platforms. Poll with getLengthAsync() instead. |
addInstanceAt() / swap() return value | Always true on iOS. Android returns correct value. |
instanceName | Empty string except for instances created via createInstanceByNameAsync(). |
defaultArtboardViewModel() on Android | Uses a heuristic. Prefer viewModelByNameAsync(name) with an explicit name. |
Migrating from v0.1.0 to v0.2.0
This change updates to a new major version of Nitro to resolve a view recycling issue. See the release for more details.
The only change required is to update the version of Nitro used in your app.
Migrating from rive-react-native to @rive-app/react-native
The new Rive React Native runtime (@rive-app/react-native) is a complete rewrite built with Nitro Modules for improved performance and better React Native integration.
All your Rive graphics will still look and function the same as they did
before.
What’s New
RiveFile Ownership: You now own the RiveFile object via the useRiveFile hook, enabling file caching, multiple instances from one file, and better resource management.
// Old: file managed internally
<Rive url="https://cdn.rive.app/animations/vehicles.riv" />
// New: you own the RiveFile
const { riveFile } = useRiveFile(require("./vehicles.riv"));
<RiveView file={riveFile} />
Enhanced Data Binding: Direct access to ViewModel and ViewModelInstance objects, enabling initialization hooks, multiple instances, and support for all property types (lists, images, artboards).
// Old: hooks take riveRef, return tuples
const [setRiveRef, riveRef] = useRive();
const [health, setHealth] = useRiveNumber(riveRef, "health");
// New: hooks take viewModelInstance, return objects
const viewModelInstance = useViewModelInstance(riveFile);
const { value: health, setValue: setHealth } = useRiveNumber(
"health",
viewModelInstance
);
Improved Error Handling:
Error handling is improved. See the error handling documentation for more information.
Requirements
- React Native: 0.78+ (0.79+ recommended)
- Expo SDK: 53+ (for Expo users)
- iOS: 15.1+
- Android: SDK 24+
- Xcode: 16.4+
- JDK: 17+
- Nitro Modules: 0.25.2+
Migration Steps
1. Installation
npm uninstall rive-react-native
npm install @rive-app/react-native react-native-nitro-modules
react-native-nitro-modules is required as this library relies on Nitro
Modules.
2. Update Imports
// Old
import Rive from "rive-react-native";
// New
import { RiveView } from "@rive-app/react-native";
3. Loading Rive Files
New Runtime
Legacy Runtime
const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv'));
// Also supports: URL string, resource name, or ArrayBuffer
return <RiveView file={riveFile} style={{ width: 400, height: 400 }} />;
<Rive source={require('./animation.riv')} />
// Also supports: url or resourceName props
See loading Rive files for more information.
4. Component Migration
New Runtime
Legacy Runtime
<RiveView
file={riveFile}
artboardName="MainArtboard"
stateMachineName="State Machine 1"
fit={Fit.Contain}
autoPlay={true}
style={{ width: 400, height: 400 }}
/>
<Rive
url="https://cdn.rive.app/animations/vehicles.riv"
artboardName="MainArtboard"
stateMachineName="State Machine 1"
fit={Fit.Contain}
autoplay={true}
style={{ width: 400, height: 400 }}
/>
See React Native runtime and props documentation for more information.
5. View Reference Migration
New Runtime
Legacy Runtime
const { riveViewRef, setHybridRef } = useRive();
riveViewRef?.play();
riveViewRef?.pause();
<RiveView hybridRef={setHybridRef} file={riveFile} />
const [setRiveRef, riveRef] = useRive();
riveRef?.play();
riveRef?.pause();
<Rive ref={setRiveRef} url="..." />
See Rive ref methods for more information.
These methods are deprecated. Migrate to data binding
instead.
New Runtime
Legacy Runtime
riveViewRef?.setNumberInputValue('level', 5);
riveViewRef?.setBooleanInputValue('isActive', true);
riveViewRef?.triggerInput('buttonPressed');
riveRef.current?.setInputState('State Machine 1', 'level', 5);
riveRef.current?.setInputState('State Machine 1', 'isActive', true);
riveRef.current?.fireState('State Machine 1', 'buttonPressed');
See state machine inputs for more information.
7. Rive Events (Deprecated)
New Runtime
Legacy Runtime
useEffect(() => {
const handleEvent = (event: RiveEvent) => console.log(event);
riveViewRef?.onEventListener(handleEvent);
return () => riveViewRef?.removeEventListeners();
}, [riveViewRef]);
<Rive
onRiveEventReceived={(event) => console.log(event)}
url="..."
/>
See runtime events for more information.
8. Data Binding
The new runtime significantly improves data binding by giving you direct access to ViewModelInstance objects. This enables:
- Initialization hooks - Set initial property values before rendering with
onInit callback
- Multiple instances - Create and manage multiple view model instances from the same file
- Advanced property types - Full support for lists, images, artboards, and nested view models
- Better React integration - Property hooks integrate seamlessly with React state and lifecycle
Main API changes:
- Property hooks take
viewModelInstance instead of riveRef
- Hooks return objects instead of tuples
- Parameters are swapped:
(path, viewModelInstance) vs (riveRef, path)
New Runtime
Legacy Runtime
const { riveFile } = useRiveFile(require('./animation.riv'));
const viewModelInstance = useViewModelInstance(riveFile);
const { value: health, setValue: setHealth } = useRiveNumber('health', viewModelInstance);
const { value: name, setValue: setName } = useRiveString('Player/Name', viewModelInstance);
const { trigger } = useRiveTrigger('gameOver', viewModelInstance, {
onTrigger: () => console.log('Game Over!')
});
<RiveView file={riveFile} dataBind={viewModelInstance} />
const [setRiveRef, riveRef] = useRive();
const [health, setHealth] = useRiveNumber(riveRef, 'health');
const [name, setName] = useRiveString(riveRef, 'Player/Name');
useRiveTrigger(riveRef, 'gameOver', () => console.log('Game Over!'));
<Rive ref={setRiveRef} resourceName="animation" dataBinding={AutoBind(true)} />
See react-native/data binding for more information.
9. Out of Band Assets
New Runtime
Legacy Runtime
const { riveFile } = useRiveFile(
require('./animation.riv'),
{
referencedAssets: {
'Inter-594377': {
source: require('./fonts/Inter-594377.ttf'),
},
'my-image': {
source: { uri: 'https://example.com/image.png' },
},
},
}
);
<Rive
url="..."
referencedAssets={{
'Inter-594377': {
source: require('./fonts/Inter-594377.ttf'),
},
}}
/>
See loading assets for more information.
10. Text Run Updates (Deprecated)
Direct text run methods (.setTextRunValue(), .getTextRunValue()) are
deprecated. Migrate to data binding strings instead.
const { value: playerName, setValue: setPlayerName } = useRiveString('playerName', viewModelInstance);
setPlayerName('John Doe');
// The new runtime still supports text run methods like the old runtime
riveViewRef?.setTextRunValue('playerName', 'John Doe');
const name = riveViewRef?.getTextRunValue('playerName');
riveRef.current?.setTextRunValue('playerName', 'John Doe');
See text runs for more information.
11. Callbacks
New Runtime
Legacy Runtime
<RiveView
file={riveFile}
onError={(error) => console.error('Rive error:', error)}
/>
// For state changes, use data binding listeners. For example, a trigger:
const { trigger } = useRiveTrigger('onStateChange', viewModelInstance, {
onTrigger: () => console.log('State changed')
});
<Rive
onPlay={(name, isSM) => console.log('Playing:', name)}
onPause={(name, isSM) => console.log('Paused:', name)}
onStateChanged={(sm, state) => console.log('State changed:', state)}
onError={(error) => console.error('Error:', error)}
/>
See data binding for more information.
Getting Help
If you encounter issues:
- Check the new runtime documentation
- Review the Data Binding guide
- See the example app
- Visit the Rive community forums
- Report issues on GitHub