Skip to content

Commit a5a7570

Browse files
docs(react-native): update hook APIs for v0.4.0 breaking changes (#588)
Update code examples and migration guide for v0.4.0 breaking changes from rive-app/rive-nitro-react-native#184: - `useViewModelInstance` returns `{ instance, error }` instead of `ViewModelInstance | null` - `useRiveFile` error is now `Error` instead of `string` - Property hooks (`useRiveNumber`, etc.) start as `undefined` Co-authored-by: Lance Snider <lance@lancesnider.com>
1 parent 8c1ea1d commit a5a7570

File tree

5 files changed

+123
-73
lines changed

5 files changed

+123
-73
lines changed

runtimes/react-native/data-binding.mdx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,26 @@ import { Demos } from "/snippets/demos.jsx";
6464
const { riveFile } = useRiveFile(require('./my_file.riv'));
6565

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

6969
// Specify artboard or ViewModel name (mutually exclusive)
70-
const instance = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
71-
const instance = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
70+
const { instance } = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
71+
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
7272

7373
// instanceName can be combined with any of the above to pick a specific instance
74-
const instance = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
75-
const instance = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });
74+
const { instance } = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
75+
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });
7676

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

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

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

102102
const { riveViewRef, setHybridRef } = useRive();
103-
const instance = useViewModelInstance(riveViewRef);
103+
const { instance } = useViewModelInstance(riveViewRef);
104104
```
105105

106106
</Tab>
@@ -179,7 +179,7 @@ import { Demos } from "/snippets/demos.jsx";
179179
import { RiveView, useRiveFile, useViewModelInstance } from '@rive-app/react-native';
180180

181181
const { riveFile } = useRiveFile(require('./my_file.riv'));
182-
const instance = useViewModelInstance(riveFile);
182+
const { instance } = useViewModelInstance(riveFile);
183183

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

229229
```tsx
230-
const instance = useViewModelInstance(riveFile);
230+
const { instance } = useViewModelInstance(riveFile);
231231
<RiveView
232232
file={riveFile}
233233
dataBind={instance}
@@ -308,7 +308,7 @@ import { Demos } from "/snippets/demos.jsx";
308308
} from '@rive-app/react-native';
309309

310310
const { riveFile } = useRiveFile(require('./my_file.riv'));
311-
const instance = useViewModelInstance(riveFile);
311+
const { instance } = useViewModelInstance(riveFile);
312312

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

444444
const { riveFile } = useRiveFile(require('./my_file.riv'));
445-
const instance = useViewModelInstance(riveFile);
445+
const { instance } = useViewModelInstance(riveFile);
446446

447447
// Accessing 'settings/theme/name' (String)
448448
const { value: themeName, setValue: setThemeName } = useRiveString(
@@ -504,7 +504,7 @@ import { Demos } from "/snippets/demos.jsx";
504504
} from '@rive-app/react-native';
505505

506506
const { riveFile } = useRiveFile(require('./my_file.riv'));
507-
const instance = useViewModelInstance(riveFile);
507+
const { instance } = useViewModelInstance(riveFile);
508508

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

586586
const { riveViewRef, setHybridRef } = useRive();
587587
const { riveFile } = useRiveFile(require('./my_file.riv'));
588-
const instance = useViewModelInstance(riveFile);
588+
const { instance } = useViewModelInstance(riveFile);
589589
const riveViewRef = useRef<RiveViewRef>(undefined);
590590

591591
const handleLoadImage = async () => {
@@ -665,7 +665,7 @@ import { Demos } from "/snippets/demos.jsx";
665665
} from '@rive-app/react-native';
666666

667667
const { riveFile } = useRiveFile(require('./my_file.riv'));
668-
const instance = useViewModelInstance(riveFile);
668+
const { instance } = useViewModelInstance(riveFile);
669669

670670
// Get the list property with manipulation functions
671671
const {
@@ -786,7 +786,7 @@ import { Demos } from "/snippets/demos.jsx";
786786
} from '@rive-app/react-native';
787787

788788
const { riveFile } = useRiveFile(require('./my_file.riv'));
789-
const instance = useViewModelInstance(riveFile);
789+
const { instance } = useViewModelInstance(riveFile);
790790

791791
const { value: category, setValue: setCategory, error } = useRiveEnum(
792792
'category',

runtimes/react-native/layouts.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ import ResponsiveLayouts from "/snippets/runtimes/layouts/responsive-layouts.mdx
111111
{isLoading ? (
112112
<ActivityIndicator size="large" color="#0000ff" />
113113
) : error ? (
114-
<Text style={styles.errorText}>{error}</Text>
114+
<Text style={styles.errorText}>{error?.message}</Text>
115115
) : riveFile ? (
116116
<RiveView
117117
hybridRef={{ f: (ref) => (riveRef.current = ref) }}

runtimes/react-native/loading-assets.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ import Resources from '/snippets/runtimes/loading-assets/resources.mdx'
111111
} else if (error != null) {
112112
return (
113113
<View style={styles.safeAreaViewContainer}>
114-
<Text>Error loading Rive file: {error}</Text>
114+
<Text>Error loading Rive file: {error?.message}</Text>
115115
</View>
116116
);
117117
}

runtimes/react-native/migration-guide.mdx

Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,91 @@ title: "Migration Guide"
33
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."
44
---
55

6+
## Migrating to `v0.4.0`+
7+
8+
This release improves error transparency and loading semantics for hooks, preparing for the async experimental runtime.
9+
10+
### `useViewModelInstance` Returns `{ instance, error }`
11+
12+
The hook now returns a discriminated union instead of `ViewModelInstance | null`:
13+
14+
- `{ instance: undefined, error: null }` — loading (source not ready)
15+
- `{ instance: ViewModelInstance, error: null }` — success
16+
- `{ instance: null, error: null }` — resolved, no ViewModel found
17+
- `{ instance: null, error: Error }` — lookup failed
18+
19+
<Tabs>
20+
<Tab title="New API">
21+
```tsx
22+
const { riveFile } = useRiveFile(require('./animation.riv'));
23+
const { instance, error } = useViewModelInstance(riveFile);
24+
25+
if (error) return <Text>{error.message}</Text>;
26+
if (!instance) return <ActivityIndicator />;
27+
return <RiveView file={riveFile} dataBind={instance} />;
28+
```
29+
30+
</Tab>
31+
<Tab title="Previous API">
32+
```tsx
33+
const { riveFile } = useRiveFile(require('./animation.riv'));
34+
const instance = useViewModelInstance(riveFile);
35+
36+
if (!instance) return <ActivityIndicator />;
37+
return <RiveView file={riveFile} dataBind={instance} />;
38+
```
39+
40+
</Tab>
41+
</Tabs>
42+
43+
### `useRiveFile` Error Is Now `Error`
44+
45+
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.
46+
47+
<Tabs>
48+
<Tab title="New API">
49+
```tsx
50+
const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv'));
51+
52+
if (error) return <Text>{error.message}</Text>;
53+
```
54+
55+
</Tab>
56+
<Tab title="Previous API">
57+
```tsx
58+
const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv'));
59+
60+
if (error) return <Text>{error}</Text>;
61+
```
62+
63+
</Tab>
64+
</Tabs>
65+
66+
### Property Hooks Start `undefined`
67+
68+
`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.
69+
70+
```tsx
71+
const { value: health } = useRiveNumber('health', instance);
72+
73+
// health is undefined on the first render — guard before using it
74+
<Text>{health !== undefined ? health.toFixed(2) : '...'}</Text>
75+
76+
// guard in updater functions
77+
setHealth((prev) => (prev ?? 0) + 1);
78+
```
79+
80+
### Quick Reference
81+
82+
| Previous | Replacement |
83+
|---|---|
84+
| `const instance = useViewModelInstance(file)` | `const { instance, error } = useViewModelInstance(file)` |
85+
| `{error}` (in JSX, from `useRiveFile`) | `{error?.message}` |
86+
| `riveFile === null` (loading check) | `riveFile === undefined` or use `isLoading` |
87+
| `health` available synchronously on first render | Guard for `undefined`: `health !== undefined ? ... : ...` |
88+
89+
---
90+
691
## Migrating to the Async API (`v0.3.2`+)
792

893
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).
@@ -12,7 +97,6 @@ This release introduces an **async-first API** to prepare for the new experiment
1297
- **Async methods** replace all synchronous ViewModel and property accessors
1398
- **Name-based access** replaces count/index-based ViewModel and artboard lookups
1499
- **`getValueAsync()` / `set()`** replace `property.value` for reading and writing properties
15-
- **`useRiveNumber` and similar hooks** now start as `undefined` until the first listener emission
16100
- **State machine inputs, text runs, and events** are deprecated and will be removed in the experimental runtime — use [data binding](/runtimes/data-binding) instead
17101

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

142-
#### 7. Hook Value Guarding
143-
144-
Property hooks (`useRiveNumber`, `useRiveString`, `useRiveBoolean`, `useRiveColor`, `useRiveEnum`) now return `undefined` as their initial value until the first listener emission.
145-
146-
<Tabs>
147-
<Tab title="New API">
148-
```tsx
149-
const { value: count, setValue: setCount } = useRiveNumber(
150-
'Counter/Value',
151-
instance
152-
);
153-
154-
// Guard for undefined in render
155-
<Text>{count !== undefined ? count.toFixed(2) : '...'}</Text>
156-
157-
// Guard in updater functions
158-
setCount((prev) => (prev ?? 0) + 1);
159-
```
160-
161-
</Tab>
162-
<Tab title="Previous API">
163-
```tsx
164-
const { value: count, setValue: setCount } = useRiveNumber(
165-
'Counter/Value',
166-
instance
167-
);
168-
169-
// Previously available synchronously on first render
170-
<Text>{count.toFixed(2)}</Text>
171-
```
172-
173-
</Tab>
174-
</Tabs>
175-
176-
#### 8. Async Setup Pattern
226+
#### 7. Async Setup Pattern
177227

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

180230
<Tabs>
181231
<Tab title="useViewModelInstance (Recommended)">
182232
```tsx
183233
const { riveFile } = useRiveFile(require('./animation.riv'));
184-
const instance = useViewModelInstance(riveFile);
234+
const { instance } = useViewModelInstance(riveFile);
185235

186236
const { value: health, setValue: setHealth } = useRiveNumber(
187237
'health',
@@ -291,7 +341,7 @@ const [setRiveRef, riveRef] = useRive();
291341
const [health, setHealth] = useRiveNumber(riveRef, "health");
292342

293343
// New: hooks take viewModelInstance, return objects
294-
const viewModelInstance = useViewModelInstance(riveFile);
344+
const { instance: viewModelInstance } = useViewModelInstance(riveFile);
295345
const { value: health, setValue: setHealth } = useRiveNumber(
296346
"health",
297347
viewModelInstance
@@ -499,7 +549,7 @@ Main API changes:
499549
<Tab title="New Runtime">
500550
```tsx
501551
const { riveFile } = useRiveFile(require('./animation.riv'));
502-
const viewModelInstance = useViewModelInstance(riveFile);
552+
const { instance: viewModelInstance } = useViewModelInstance(riveFile);
503553

504554
const { value: health, setValue: setHealth } = useRiveNumber('health', viewModelInstance);
505555
const { value: name, setValue: setName } = useRiveString('Player/Name', viewModelInstance);

runtimes/react-native/react-native.mdx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ This guide documents how to get started using the Rive React Native runtime. The
224224
require('path/to/quick_start.riv')
225225
);
226226
const { riveViewRef, setHybridRef } = useRive();
227-
const viewModelInstance = useViewModelInstance(riveFile, {
227+
const { instance: viewModelInstance } = useViewModelInstance(riveFile, {
228228
onInit: (vmi) => (vmi.numberProperty('health')!.value = 20),
229229
});
230230

@@ -252,7 +252,7 @@ This guide documents how to get started using the Rive React Native runtime. The
252252
require('path/to/quick_start.riv')
253253
);
254254
const { riveViewRef, setHybridRef } = useRive();
255-
const viewModelInstance = useViewModelInstance(riveFile, {
255+
const { instance: viewModelInstance } = useViewModelInstance(riveFile, {
256256
onInit: (vmi) => (vmi.numberProperty('health')!.value = 20),
257257
});
258258

@@ -376,25 +376,25 @@ This guide documents how to get started using the Rive React Native runtime. The
376376

377377
```ts
378378
// From RiveFile — default artboard's ViewModel, default instance
379-
const instance = useViewModelInstance(riveFile);
379+
const { instance } = useViewModelInstance(riveFile);
380380

381381
// From RiveFile — specify artboard or ViewModel name (mutually exclusive)
382-
const instance = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
383-
const instance = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
382+
const { instance } = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
383+
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
384384

385385
// instanceName can be combined with any of the above to pick a specific instance
386-
const instance = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
387-
const instance = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });
386+
const { instance } = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
387+
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });
388388

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

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

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

419419
const { riveViewRef, setHybridRef } = useRive();
420-
const instance = useViewModelInstance(riveViewRef);
420+
const { instance } = useViewModelInstance(riveViewRef);
421421
```
422422

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

0 commit comments

Comments
 (0)