Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions runtimes/react-native/data-binding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,26 @@ import { Demos } from "/snippets/demos.jsx";
const { riveFile } = useRiveFile(require('./my_file.riv'));

// From RiveFile — default artboard's ViewModel, default instance
const instance = useViewModelInstance(riveFile);
const { instance } = useViewModelInstance(riveFile);

// Specify artboard or ViewModel name (mutually exclusive)
const instance = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
const instance = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
const { instance } = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
const { instance } = useViewModelInstance(riveFile, { 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 } = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });

// From a ViewModel object
const viewModel = await riveFile?.viewModelByNameAsync('My View Model');
const namedInstance = useViewModelInstance(viewModel, { name: 'My Instance' });
const newInstance = useViewModelInstance(viewModel, { useNew: true });
const viewModel = riveFile?.viewModelByName('My View Model');
const { instance: namedInstance } = useViewModelInstance(viewModel, { name: 'My Instance' });
const { instance: newInstance } = useViewModelInstance(viewModel, { useNew: true });

// With required: true (throws if null, use with Error Boundary)
const instance = useViewModelInstance(riveFile, { required: true });
const { instance } = useViewModelInstance(riveFile, { required: true });

// With onInit to set initial values
const instance = useViewModelInstance(riveFile, {
// With onInit to set initial values synchronously
const { instance } = useViewModelInstance(riveFile, {
onInit: (vmi) => {
vmi.numberProperty('health')?.set(100);
},
Expand All @@ -100,7 +100,7 @@ import { Demos } from "/snippets/demos.jsx";
import { useRive, useViewModelInstance } from '@rive-app/react-native';

const { riveViewRef, setHybridRef } = useRive();
const instance = useViewModelInstance(riveViewRef);
const { instance } = useViewModelInstance(riveViewRef);
```

</Tab>
Expand Down Expand Up @@ -179,7 +179,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 } = useViewModelInstance(riveFile);

return (
<RiveView
Expand Down Expand Up @@ -227,7 +227,7 @@ import { Demos } from "/snippets/demos.jsx";
Or bind a specific instance:

```tsx
const instance = useViewModelInstance(riveFile);
const { instance } = useViewModelInstance(riveFile);
<RiveView
file={riveFile}
dataBind={instance}
Expand Down Expand Up @@ -308,7 +308,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 } = useViewModelInstance(riveFile);

// Boolean
const { value: isActive, setValue: setIsActive, error: boolError } = useRiveBoolean(
Expand Down Expand Up @@ -442,7 +442,7 @@ import { Demos } from "/snippets/demos.jsx";
import { useRiveString, useRiveNumber, useRiveFile, useViewModelInstance } from '@rive-app/react-native';

const { riveFile } = useRiveFile(require('./my_file.riv'));
const instance = useViewModelInstance(riveFile);
const { instance } = useViewModelInstance(riveFile);

// Accessing 'settings/theme/name' (String)
const { value: themeName, setValue: setThemeName } = useRiveString(
Expand Down Expand Up @@ -504,7 +504,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 } = useViewModelInstance(riveFile);

const { value: boolValue, setValue: setBoolValue } = useRiveBoolean('My Boolean Property', instance);
const { value: stringValue, setValue: setStringValue } = useRiveString('My String Property', instance);
Expand Down Expand Up @@ -585,7 +585,7 @@ import { Demos } from "/snippets/demos.jsx";

const { riveViewRef, setHybridRef } = useRive();
const { riveFile } = useRiveFile(require('./my_file.riv'));
const instance = useViewModelInstance(riveFile);
const { instance } = useViewModelInstance(riveFile);
const riveViewRef = useRef<RiveViewRef>(undefined);

const handleLoadImage = async () => {
Expand Down Expand Up @@ -665,7 +665,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 } = useViewModelInstance(riveFile);

// Get the list property with manipulation functions
const {
Expand Down Expand Up @@ -786,7 +786,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 } = useViewModelInstance(riveFile);

const { value: category, setValue: setCategory, error } = useRiveEnum(
'category',
Expand Down
2 changes: 1 addition & 1 deletion runtimes/react-native/layouts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ import ResponsiveLayouts from "/snippets/runtimes/layouts/responsive-layouts.mdx
{isLoading ? (
<ActivityIndicator size="large" color="#0000ff" />
) : error ? (
<Text style={styles.errorText}>{error}</Text>
<Text style={styles.errorText}>{error?.message}</Text>
) : riveFile ? (
<RiveView
hybridRef={{ f: (ref) => (riveRef.current = ref) }}
Expand Down
2 changes: 1 addition & 1 deletion runtimes/react-native/loading-assets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ import Resources from '/snippets/runtimes/loading-assets/resources.mdx'
} else if (error != null) {
return (
<View style={styles.safeAreaViewContainer}>
<Text>Error loading Rive file: {error}</Text>
<Text>Error loading Rive file: {error?.message}</Text>
</View>
);
}
Expand Down
133 changes: 94 additions & 39 deletions runtimes/react-native/migration-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,91 @@ 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.0`+

This release improves error transparency and loading semantics for hooks, preparing for the async experimental runtime.

### `useViewModelInstance` Returns `{ instance, error }`

The hook now returns a discriminated union instead of `ViewModelInstance | null`:

- `{ instance: undefined, error: null }` — loading (source not ready)
- `{ instance: ViewModelInstance, error: null }` — success
- `{ instance: null, error: null }` — resolved, no ViewModel found
- `{ instance: null, error: Error }` — lookup failed

<Tabs>
<Tab title="New API">
```tsx
const { riveFile } = useRiveFile(require('./animation.riv'));
const { instance, error } = useViewModelInstance(riveFile);

if (error) return <Text>{error.message}</Text>;
if (!instance) return <ActivityIndicator />;
return <RiveView file={riveFile} dataBind={instance} />;
```

</Tab>
<Tab title="Previous API">
```tsx
const { riveFile } = useRiveFile(require('./animation.riv'));
const instance = useViewModelInstance(riveFile);

if (!instance) return <ActivityIndicator />;
return <RiveView file={riveFile} dataBind={instance} />;
```

</Tab>
</Tabs>

### `useRiveFile` Error Is Now `Error`

The `error` field is now an `Error` object instead of a `string`, and `riveFile` is `undefined` while loading (was `null`). `isLoading` is kept for convenience.

<Tabs>
<Tab title="New API">
```tsx
const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv'));

if (error) return <Text>{error.message}</Text>;
```

</Tab>
<Tab title="Previous API">
```tsx
const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv'));

if (error) return <Text>{error}</Text>;
```

</Tab>
</Tabs>

### Property Hooks Start `undefined`

`useRiveNumber`, `useRiveString`, `useRiveBoolean`, `useRiveColor`, and `useRiveEnum` no longer read `property.value` synchronously on mount. The value starts as `undefined` and arrives via the property listener.

```tsx
const { value: health } = useRiveNumber('health', instance);

// health is undefined on the first render — guard before using it
<Text>{health !== undefined ? health.toFixed(2) : '...'}</Text>

// guard in updater functions
setHealth((prev) => (prev ?? 0) + 1);
```

### Quick Reference

| Previous | Replacement |
|---|---|
| `const instance = useViewModelInstance(file)` | `const { instance, error } = useViewModelInstance(file)` |
| `{error}` (in JSX, from `useRiveFile`) | `{error?.message}` |
| `riveFile === null` (loading check) | `riveFile === undefined` or use `isLoading` |
| `health` available synchronously on first render | Guard for `undefined`: `health !== undefined ? ... : ...` |

---

## 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](/runtimes/data-binding) and will be removed entirely in the experimental runtime (and therefore in upcoming `@rive-app/react-native` versions).
Expand All @@ -12,7 +97,6 @@ This release introduces an **async-first API** to prepare for the new experiment
- **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](/runtimes/data-binding) instead

### Migration Steps
Expand Down Expand Up @@ -139,49 +223,15 @@ This release introduces an **async-first API** to prepare for the new experiment
</Tab>
</Tabs>

#### 7. Hook Value Guarding

Property hooks (`useRiveNumber`, `useRiveString`, `useRiveBoolean`, `useRiveColor`, `useRiveEnum`) now return `undefined` as their initial value until the first listener emission.

<Tabs>
<Tab title="New API">
```tsx
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);
```

</Tab>
<Tab title="Previous API">
```tsx
const { value: count, setValue: setCount } = useRiveNumber(
'Counter/Value',
instance
);

// Previously available synchronously on first render
<Text>{count.toFixed(2)}</Text>
```

</Tab>
</Tabs>

#### 8. Async Setup Pattern
#### 7. Async Setup Pattern

Synchronous `useMemo` chains for ViewModel setup should be replaced with `useState` + `useEffect`, or simplified with the `useViewModelInstance` hook.

<Tabs>
<Tab title="useViewModelInstance (Recommended)">
```tsx
const { riveFile } = useRiveFile(require('./animation.riv'));
const instance = useViewModelInstance(riveFile);
const { instance } = useViewModelInstance(riveFile);

const { value: health, setValue: setHealth } = useRiveNumber(
'health',
Expand Down Expand Up @@ -242,6 +292,11 @@ Synchronous `useMemo` chains for ViewModel setup should be replaced with `useSta
| `instance.viewModel(path)` | `await instance.viewModelAsync(path)` |
| `listProp.length` | `await listProp.getLengthAsync()` |
| `listProp.getInstanceAt(i)` | `await listProp.getInstanceAtAsync(i)` |
| `listProp.addInstance(inst)` | `await listProp.addInstanceAsync(inst)` |
| `listProp.addInstanceAt(inst, i)` | `await listProp.addInstanceAtAsync(inst, i)` |
| `listProp.removeInstance(inst)` | `await listProp.removeInstanceAsync(inst)` |
| `listProp.removeInstanceAt(i)` | `await listProp.removeInstanceAtAsync(i)` |
| `listProp.swap(i, j)` | `await listProp.swapAsync(i, j)` |
| `prop.value` (read) | `await prop.getValueAsync()` |
| `prop.value = x` (write) | `prop.set(x)` |

Expand Down Expand Up @@ -291,7 +346,7 @@ const [setRiveRef, riveRef] = useRive();
const [health, setHealth] = useRiveNumber(riveRef, "health");

// New: hooks take viewModelInstance, return objects
const viewModelInstance = useViewModelInstance(riveFile);
const { instance: viewModelInstance } = useViewModelInstance(riveFile);
const { value: health, setValue: setHealth } = useRiveNumber(
"health",
viewModelInstance
Expand Down Expand Up @@ -499,7 +554,7 @@ Main API changes:
<Tab title="New Runtime">
```tsx
const { riveFile } = useRiveFile(require('./animation.riv'));
const viewModelInstance = useViewModelInstance(riveFile);
const { instance: viewModelInstance } = useViewModelInstance(riveFile);

const { value: health, setValue: setHealth } = useRiveNumber('health', viewModelInstance);
const { value: name, setValue: setName } = useRiveString('Player/Name', viewModelInstance);
Expand Down
30 changes: 15 additions & 15 deletions runtimes/react-native/react-native.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ This guide documents how to get started using the Rive React Native runtime. The
require('path/to/quick_start.riv')
);
const { riveViewRef, setHybridRef } = useRive();
const viewModelInstance = useViewModelInstance(riveFile, {
onInit: (vmi) => (vmi.numberProperty('health')!.value = 20),
const { instance: viewModelInstance } = useViewModelInstance(riveFile, {
onInit: (vmi) => vmi.numberProperty('health')!.set(20),
});

return (
Expand All @@ -252,8 +252,8 @@ This guide documents how to get started using the Rive React Native runtime. The
require('path/to/quick_start.riv')
);
const { riveViewRef, setHybridRef } = useRive();
const viewModelInstance = useViewModelInstance(riveFile, {
onInit: (vmi) => (vmi.numberProperty('health')!.value = 20),
const { instance: viewModelInstance } = useViewModelInstance(riveFile, {
onInit: (vmi) => vmi.numberProperty('health')!.set(20),
});

const { value: health, setValue: setHealth } = useRiveNumber(
Expand Down Expand Up @@ -376,27 +376,27 @@ This guide documents how to get started using the Rive React Native runtime. The

```ts
// From RiveFile — default artboard's ViewModel, default instance
const instance = useViewModelInstance(riveFile);
const { instance } = useViewModelInstance(riveFile);

// From RiveFile — specify artboard or ViewModel name (mutually exclusive)
const instance = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
const instance = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
const { instance } = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
const { instance } = useViewModelInstance(riveFile, { 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 } = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });

// From a ViewModel object
const namedInstance = useViewModelInstance(viewModel, { name: 'My Instance' });
const newInstance = useViewModelInstance(viewModel, { useNew: true });
const { instance: namedInstance } = useViewModelInstance(viewModel, { name: 'My Instance' });
const { instance: newInstance } = useViewModelInstance(viewModel, { useNew: true });

// With required: true (throws if null, use with Error Boundary)
const instance = useViewModelInstance(riveFile, { required: true });
const { instance } = useViewModelInstance(riveFile, { required: true });

// With onInit to set initial values synchronously
const instance = useViewModelInstance(riveFile, {
const { instance } = useViewModelInstance(riveFile, {
onInit: (vmi) => {
vmi.numberProperty('health')!.value = 100;
vmi.numberProperty('health')!.set(100);
},
});
```
Expand All @@ -417,7 +417,7 @@ This guide documents how to get started using the Rive React Native runtime. The
import { useRive, useViewModelInstance } from '@rive-app/react-native';

const { riveViewRef, setHybridRef } = useRive();
const instance = useViewModelInstance(riveViewRef);
const { instance } = useViewModelInstance(riveViewRef);
```

See the [runtime data binding documentation](/runtimes/react-native/data-binding) for more information.
Expand Down