diff --git a/runtimes/react-native/data-binding.mdx b/runtimes/react-native/data-binding.mdx index 5ac25e13..6314fd66 100644 --- a/runtimes/react-native/data-binding.mdx +++ b/runtimes/react-native/data-binding.mdx @@ -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); }, @@ -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); ``` @@ -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 ( (undefined); const handleLoadImage = async () => { @@ -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 { @@ -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', diff --git a/runtimes/react-native/layouts.mdx b/runtimes/react-native/layouts.mdx index 6678793a..0f22815b 100644 --- a/runtimes/react-native/layouts.mdx +++ b/runtimes/react-native/layouts.mdx @@ -111,7 +111,7 @@ import ResponsiveLayouts from "/snippets/runtimes/layouts/responsive-layouts.mdx {isLoading ? ( ) : error ? ( - {error} + {error?.message} ) : riveFile ? ( (riveRef.current = ref) }} diff --git a/runtimes/react-native/loading-assets.mdx b/runtimes/react-native/loading-assets.mdx index 3acb7e54..9248b0d5 100644 --- a/runtimes/react-native/loading-assets.mdx +++ b/runtimes/react-native/loading-assets.mdx @@ -111,7 +111,7 @@ import Resources from '/snippets/runtimes/loading-assets/resources.mdx' } else if (error != null) { return ( - Error loading Rive file: {error} + Error loading Rive file: {error?.message} ); } diff --git a/runtimes/react-native/migration-guide.mdx b/runtimes/react-native/migration-guide.mdx index 54f5a978..0660774a 100644 --- a/runtimes/react-native/migration-guide.mdx +++ b/runtimes/react-native/migration-guide.mdx @@ -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 + + + + ```tsx + const { riveFile } = useRiveFile(require('./animation.riv')); + const { instance, error } = useViewModelInstance(riveFile); + + if (error) return {error.message}; + if (!instance) return ; + return ; + ``` + + + + ```tsx + const { riveFile } = useRiveFile(require('./animation.riv')); + const instance = useViewModelInstance(riveFile); + + if (!instance) return ; + return ; + ``` + + + + +### `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. + + + + ```tsx + const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv')); + + if (error) return {error.message}; + ``` + + + + ```tsx + const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv')); + + if (error) return {error}; + ``` + + + + +### 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 +{health !== undefined ? health.toFixed(2) : '...'} + +// 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). @@ -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 @@ -139,41 +223,7 @@ This release introduces an **async-first API** to prepare for the new experiment -#### 7. Hook Value Guarding - -Property hooks (`useRiveNumber`, `useRiveString`, `useRiveBoolean`, `useRiveColor`, `useRiveEnum`) now return `undefined` as their initial value until the first listener emission. - - - - ```tsx - const { value: count, setValue: setCount } = useRiveNumber( - 'Counter/Value', - instance - ); - - // Guard for undefined in render - {count !== undefined ? count.toFixed(2) : '...'} - - // Guard in updater functions - setCount((prev) => (prev ?? 0) + 1); - ``` - - - - ```tsx - const { value: count, setValue: setCount } = useRiveNumber( - 'Counter/Value', - instance - ); - - // Previously available synchronously on first render - {count.toFixed(2)} - ``` - - - - -#### 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. @@ -181,7 +231,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 } = useViewModelInstance(riveFile); const { value: health, setValue: setHealth } = useRiveNumber( 'health', @@ -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)` | @@ -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 @@ -499,7 +554,7 @@ Main API changes: ```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); diff --git a/runtimes/react-native/react-native.mdx b/runtimes/react-native/react-native.mdx index 1a7b1394..33b68e16 100644 --- a/runtimes/react-native/react-native.mdx +++ b/runtimes/react-native/react-native.mdx @@ -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 ( @@ -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( @@ -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); }, }); ``` @@ -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.