diff --git a/runtimes/react-native/data-binding.mdx b/runtimes/react-native/data-binding.mdx
index 6314fd66..75816324 100644
--- a/runtimes/react-native/data-binding.mdx
+++ b/runtimes/react-native/data-binding.mdx
@@ -58,49 +58,63 @@ import { Demos } from "/snippets/demos.jsx";
Use the `useViewModelInstance` hook to create a view model instance. You can pass a `RiveFile`, `ViewModel`, or `RiveViewRef` as the source.
+ Pass `async: true` to opt in to asynchronous instance creation. The flag signals that your component handles the loading state: the hook returns `{ instance, isLoading, error }` and the instance is not available on the first render, so gate rendering on it. This prepares your code for the new runtime implementation, where creating an instance is an asynchronous operation.
+
```tsx
+ import { Text } from 'react-native';
import { useRiveFile, useViewModelInstance, RiveView } from '@rive-app/react-native';
- const { riveFile } = useRiveFile(require('./my_file.riv'));
+ const { riveFile, error: fileError } = useRiveFile(require('./my_file.riv'));
// From RiveFile — default artboard's ViewModel, default instance
- const { instance } = useViewModelInstance(riveFile);
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
+
+ if (fileError || error) return {(fileError ?? error)!.message};
+
+ return riveFile && instance && (
+
+ );
+ ```
+
+ All lookup options combine with `async: true`:
+ ```tsx
// Specify artboard or ViewModel name (mutually exclusive)
- const { instance } = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
- const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, artboardName: 'MainArtboard' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, viewModelName: 'Settings' });
// instanceName can be combined with any of the above to pick a specific instance
- const { instance } = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
- const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, instanceName: 'PersonInstance' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, viewModelName: 'Settings', instanceName: 'UserSettings' });
// From a ViewModel object
- const viewModel = riveFile?.viewModelByName('My View Model');
- const { instance: namedInstance } = useViewModelInstance(viewModel, { name: 'My Instance' });
- const { instance: newInstance } = useViewModelInstance(viewModel, { useNew: true });
+ const viewModel = await riveFile?.viewModelByNameAsync('My View Model');
+ const { instance: namedInstance, isLoading, error } = useViewModelInstance(viewModel, { async: true, name: 'My Instance' });
+ const { instance: newInstance, isLoading, error } = useViewModelInstance(viewModel, { async: true, useNew: true });
- // With required: true (throws if null, use with Error Boundary)
- const { instance } = useViewModelInstance(riveFile, { required: true });
+ // With required: true (throws once resolved to null, use with Error Boundary)
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, required: true });
- // With onInit to set initial values synchronously
- const { instance } = useViewModelInstance(riveFile, {
+ // With onInit to set initial values before the instance is exposed or bound
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, {
+ async: true,
onInit: (vmi) => {
vmi.numberProperty('health')?.set(100);
},
});
-
- return (
-
- );
```
- You can also get the auto-bound instance from a `RiveViewRef`:
+
+ `async: true` is available in `@rive-app/react-native@0.4.18` and later. The synchronous creation path is deprecated and will be removed in a future release.
+
+
+ You can also get the auto-bound instance from a `RiveViewRef` — the hook waits for the view's auto-bound instance to become available:
```tsx
import { useRive, useViewModelInstance } from '@rive-app/react-native';
const { riveViewRef, setHybridRef } = useRive();
- const { instance } = useViewModelInstance(riveViewRef);
+ const { instance, isLoading, error } = useViewModelInstance(riveViewRef, { async: true });
```
@@ -179,7 +193,7 @@ import { Demos } from "/snippets/demos.jsx";
import { RiveView, useRiveFile, useViewModelInstance } from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
- const { instance } = useViewModelInstance(riveFile);
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
return (
(undefined);
const handleLoadImage = async () => {
@@ -665,7 +679,7 @@ import { Demos } from "/snippets/demos.jsx";
} from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
- const { instance } = useViewModelInstance(riveFile);
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
// Get the list property with manipulation functions
const {
@@ -786,7 +800,7 @@ import { Demos } from "/snippets/demos.jsx";
} from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
- const { instance } = useViewModelInstance(riveFile);
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
const { value: category, setValue: setCategory, error } = useRiveEnum(
'category',
diff --git a/runtimes/react-native/migration-guide.mdx b/runtimes/react-native/migration-guide.mdx
index 0660774a..bc5be17a 100644
--- a/runtimes/react-native/migration-guide.mdx
+++ b/runtimes/react-native/migration-guide.mdx
@@ -3,6 +3,66 @@ title: "Migration Guide"
description: "Learn how to migrate your React Native app when upgrading between major versions of the Rive React Native runtime, including breaking changes and new features."
---
+## Migrating to `v0.4.18`+
+
+This release introduces async view model instance creation and deprecates the synchronous creation path.
+
+### `useViewModelInstance` Gains `async: true` and `isLoading`
+
+Pass `async: true` to opt in to asynchronous instance creation. The flag signals that your component handles the loading state: the result now includes `isLoading`, and the instance is not available on the first render — gate rendering on it. This prepares your code for the new runtime implementation, where creating an instance is an asynchronous operation.
+
+`async: true` is a transitional flag; the synchronous creation path is being phased out in stages:
+
+1. **Now** — calls without `async: true` are deprecated in TypeScript (deprecation warnings in your editor and via lint rules).
+2. **Next** — calling without `async: true` will additionally log a runtime deprecation warning.
+3. **Later** — the synchronous path is removed and `async: true` becomes required.
+4. **Finally** — the flag itself is removed; the hook is simply asynchronous.
+
+
+
+ ```tsx
+ const { riveFile, error: fileError } = useRiveFile(require('./animation.riv'));
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
+
+ if (fileError || error) return {(fileError ?? error)!.message};
+ if (isLoading || !instance) return ;
+ return ;
+ ```
+
+
+
+ ```tsx
+ const { riveFile } = useRiveFile(require('./animation.riv'));
+ const { instance, error } = useViewModelInstance(riveFile);
+
+ if (error) return {error.message};
+ if (!instance) return ;
+ return ;
+ ```
+
+
+
+
+`async` must stay constant for the lifetime of the component — remount (e.g. change `key`) to switch modes.
+
+### `useRive` Ref Starts `undefined`
+
+`useRive().riveViewRef` starts as `undefined` (view pending) instead of `null` (failed/detached), mirroring the `useRiveFile` convention. Code using `riveViewRef === null` as a "not ready yet" check should use `riveViewRef == null` or optional chaining instead.
+
+### Other Changes
+
+- On the synchronous (deprecated) path, a `null` source now settles to a terminal `{ instance: null, isLoading: false }` instead of reporting a loading state indefinitely.
+- On Android, reading the auto-bound instance from a view ref right after mount can briefly resolve `null` while binding completes. The `async: true` path waits for the instance; one-shot reads via the deprecated sync path should be avoided.
+
+### Quick Reference
+
+| Previous | Replacement |
+|---|---|
+| `const { instance, error } = useViewModelInstance(file)` | `const { instance, isLoading, error } = useViewModelInstance(file, { async: true })` |
+| `riveViewRef === null` (not-ready check) | `riveViewRef == null` |
+
+---
+
## Migrating to `v0.4.0`+
This release improves error transparency and loading semantics for hooks, preparing for the async experimental runtime.
@@ -231,7 +291,7 @@ Synchronous `useMemo` chains for ViewModel setup should be replaced with `useSta
```tsx
const { riveFile } = useRiveFile(require('./animation.riv'));
- const { instance } = useViewModelInstance(riveFile);
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
const { value: health, setValue: setHealth } = useRiveNumber(
'health',
@@ -346,7 +406,7 @@ const [setRiveRef, riveRef] = useRive();
const [health, setHealth] = useRiveNumber(riveRef, "health");
// New: hooks take viewModelInstance, return objects
-const { instance: viewModelInstance } = useViewModelInstance(riveFile);
+const { instance: viewModelInstance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
const { value: health, setValue: setHealth } = useRiveNumber(
"health",
viewModelInstance
@@ -554,7 +614,7 @@ Main API changes:
```tsx
const { riveFile } = useRiveFile(require('./animation.riv'));
- const { instance: viewModelInstance } = useViewModelInstance(riveFile);
+ const { instance: viewModelInstance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
const { value: health, setValue: setHealth } = useRiveNumber('health', viewModelInstance);
const { value: name, setValue: setName } = useRiveString('Player/Name', viewModelInstance);
diff --git a/runtimes/react-native/react-native.mdx b/runtimes/react-native/react-native.mdx
index d9a2ceaf..1d840c8a 100644
--- a/runtimes/react-native/react-native.mdx
+++ b/runtimes/react-native/react-native.mdx
@@ -218,13 +218,14 @@ This guide documents how to get started using the Rive React Native runtime. The
This approach lets you set initial property values in the `onInit` callback before the view loads and decouples the `ViewModelInstance` from the `RiveView`.
- ```ts Manually create view model instance focus={6-8, 12, 18}
+ ```ts Manually create view model instance focus={6-9, 13, 19}
export default function QuickStart() {
const { riveFile } = useRiveFile(
require('path/to/quick_start.riv')
);
const { riveViewRef, setHybridRef } = useRive();
- const { instance: viewModelInstance } = useViewModelInstance(riveFile, {
+ const { instance: viewModelInstance, isLoading, error } = useViewModelInstance(riveFile, {
+ async: true,
onInit: (vmi) => vmi.numberProperty('health')!.set(20),
});
@@ -246,13 +247,14 @@ This guide documents how to get started using the Rive React Native runtime. The
Use the view model property hooks to update and listen to property changes.
- ```ts Property hooks focus={10-37, 50-52} expandable
+ ```ts Property hooks focus={11-38, 51-53} expandable
export default function QuickStart() {
const { riveFile } = useRiveFile(
require('path/to/quick_start.riv')
);
const { riveViewRef, setHybridRef } = useRive();
- const { instance: viewModelInstance } = useViewModelInstance(riveFile, {
+ const { instance: viewModelInstance, isLoading, error } = useViewModelInstance(riveFile, {
+ async: true,
onInit: (vmi) => vmi.numberProperty('health')!.set(20),
});
@@ -372,35 +374,42 @@ This guide documents how to get started using the Rive React Native runtime. The
### `useViewModelInstance`
- Hook to create a view model instance from a `RiveFile`, `ViewModel`, or `RiveViewRef`:
+ Hook to create a view model instance from a `RiveFile`, `ViewModel`, or `RiveViewRef`.
+
+ Pass `async: true` to opt in to asynchronous instance creation — the flag signals that your component handles the loading state. The hook returns `{ instance, isLoading, error }` and the instance is not available on the first render. This prepares your code for the new runtime implementation, where creating an instance is an asynchronous operation.
```ts
// From RiveFile — default artboard's ViewModel, default instance
- const { instance } = useViewModelInstance(riveFile);
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true });
// From RiveFile — specify artboard or ViewModel name (mutually exclusive)
- const { instance } = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
- const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, artboardName: 'MainArtboard' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, viewModelName: 'Settings' });
// instanceName can be combined with any of the above to pick a specific instance
- const { instance } = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
- const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, instanceName: 'PersonInstance' });
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, viewModelName: 'Settings', instanceName: 'UserSettings' });
// From a ViewModel object
- const { instance: namedInstance } = useViewModelInstance(viewModel, { name: 'My Instance' });
- const { instance: newInstance } = useViewModelInstance(viewModel, { useNew: true });
+ const { instance: namedInstance, isLoading, error } = useViewModelInstance(viewModel, { async: true, name: 'My Instance' });
+ const { instance: newInstance, isLoading, error } = useViewModelInstance(viewModel, { async: true, useNew: true });
- // With required: true (throws if null, use with Error Boundary)
- const { instance } = useViewModelInstance(riveFile, { required: true });
+ // With required: true (throws once resolved to null, use with Error Boundary)
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, { async: true, required: true });
- // With onInit to set initial values synchronously
- const { instance } = useViewModelInstance(riveFile, {
+ // With onInit to set initial values before the instance is exposed or bound
+ const { instance, isLoading, error } = useViewModelInstance(riveFile, {
+ async: true,
onInit: (vmi) => {
vmi.numberProperty('health')!.set(100);
},
});
```
+
+ `async: true` is available in `@rive-app/react-native@0.4.18` and later. The synchronous creation path is deprecated and will be removed in a future release.
+
+
Pass the `dataBind` prop in `RiveView`.
```ts
return (
@@ -411,13 +420,13 @@ This guide documents how to get started using the Rive React Native runtime. The
);
```
- You can also get the auto-bound instance from a `RiveViewRef`:
+ You can also get the auto-bound instance from a `RiveViewRef` — the hook waits for the view's auto-bound instance to become available:
```javascript
import { useRive, useViewModelInstance } from '@rive-app/react-native';
const { riveViewRef, setHybridRef } = useRive();
- const { instance } = useViewModelInstance(riveViewRef);
+ const { instance, isLoading, error } = useViewModelInstance(riveViewRef, { async: true });
```
See the [runtime data binding documentation](/runtimes/react-native/data-binding) for more information.