From 53f6885a9cb129ebab40fa93f1c84f14181b93d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Tue, 31 Mar 2026 07:19:59 +0200 Subject: [PATCH 1/5] docs(react-native): update hook APIs for v0.4.0 breaking changes Update code examples and migration guide for useViewModelInstance returning { instance, error }, useRiveFile error becoming Error object, and property hooks starting as undefined. --- runtimes/react-native/data-binding.mdx | 36 +++--- runtimes/react-native/layouts.mdx | 2 +- runtimes/react-native/loading-assets.mdx | 2 +- runtimes/react-native/migration-guide.mdx | 128 +++++++++++++++------- runtimes/react-native/react-native.mdx | 24 ++-- 5 files changed, 121 insertions(+), 71 deletions(-) diff --git a/runtimes/react-native/data-binding.mdx b/runtimes/react-native/data-binding.mdx index ff94fbf5..6f92f964 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 = riveFile?.viewModelByName('My View Model'); - 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; }, @@ -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..2bdf9881 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', @@ -291,7 +341,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 +549,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..6ad2b9fa 100644 --- a/runtimes/react-native/react-native.mdx +++ b/runtimes/react-native/react-native.mdx @@ -224,7 +224,7 @@ 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, { + const { instance: viewModelInstance } = useViewModelInstance(riveFile, { onInit: (vmi) => (vmi.numberProperty('health')!.value = 20), }); @@ -252,7 +252,7 @@ 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, { + const { instance: viewModelInstance } = useViewModelInstance(riveFile, { onInit: (vmi) => (vmi.numberProperty('health')!.value = 20), }); @@ -376,25 +376,25 @@ 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; }, @@ -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. From 8a472f6a69d855b4e35095da0962fd5b9f161e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Thu, 28 May 2026 08:14:48 +0200 Subject: [PATCH 2/5] docs(react-native): add Windows CMake long-path troubleshooting --- runtimes/react-native/react-native.mdx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/runtimes/react-native/react-native.mdx b/runtimes/react-native/react-native.mdx index 6ad2b9fa..af2a71ab 100644 --- a/runtimes/react-native/react-native.mdx +++ b/runtimes/react-native/react-native.mdx @@ -422,6 +422,20 @@ This guide documents how to get started using the Rive React Native runtime. The See the [runtime data binding documentation](/runtimes/react-native/data-binding) for more information. + ## Troubleshooting + + ### Android build fails on Windows (CMake long-path error) + + On Windows, the Android build can fail with `ninja: error: mkdir(CMakeFiles/rive.dir/...): No such file or directory` because paths exceed the Windows `MAX_PATH` (260-character) limit. This is a [known issue across React Native libraries that use CMake](https://docs.swmansion.com/react-native-reanimated/docs/guides/building-on-windows/). + + To fix this, set the `CMAKE_VERSION` environment variable to a newer CMake version (e.g. `3.31.6`) before building: + + ```bash + set CMAKE_VERSION=3.31.6 + ``` + + See the [Reanimated docs on building on Windows](https://docs.swmansion.com/react-native-reanimated/docs/guides/building-on-windows/) for full setup instructions. + ## Resources From e06817cd0943292c9e937c738a2abbcb8cfcdf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Thu, 28 May 2026 08:25:16 +0200 Subject: [PATCH 3/5] Revert "docs(react-native): add Windows CMake long-path troubleshooting" This reverts commit 8a472f6a69d855b4e35095da0962fd5b9f161e34. --- runtimes/react-native/react-native.mdx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/runtimes/react-native/react-native.mdx b/runtimes/react-native/react-native.mdx index af2a71ab..6ad2b9fa 100644 --- a/runtimes/react-native/react-native.mdx +++ b/runtimes/react-native/react-native.mdx @@ -422,20 +422,6 @@ This guide documents how to get started using the Rive React Native runtime. The See the [runtime data binding documentation](/runtimes/react-native/data-binding) for more information. - ## Troubleshooting - - ### Android build fails on Windows (CMake long-path error) - - On Windows, the Android build can fail with `ninja: error: mkdir(CMakeFiles/rive.dir/...): No such file or directory` because paths exceed the Windows `MAX_PATH` (260-character) limit. This is a [known issue across React Native libraries that use CMake](https://docs.swmansion.com/react-native-reanimated/docs/guides/building-on-windows/). - - To fix this, set the `CMAKE_VERSION` environment variable to a newer CMake version (e.g. `3.31.6`) before building: - - ```bash - set CMAKE_VERSION=3.31.6 - ``` - - See the [Reanimated docs on building on Windows](https://docs.swmansion.com/react-native-reanimated/docs/guides/building-on-windows/) for full setup instructions. - ## Resources From 6028f35750cecadd523758aad8e705915cd0445b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Thu, 28 May 2026 08:41:47 +0200 Subject: [PATCH 4/5] docs(react-native): use non-deprecated .set() instead of .value in onInit examples --- runtimes/react-native/react-native.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtimes/react-native/react-native.mdx b/runtimes/react-native/react-native.mdx index 6ad2b9fa..33b68e16 100644 --- a/runtimes/react-native/react-native.mdx +++ b/runtimes/react-native/react-native.mdx @@ -225,7 +225,7 @@ This guide documents how to get started using the Rive React Native runtime. The ); const { riveViewRef, setHybridRef } = useRive(); const { instance: viewModelInstance } = useViewModelInstance(riveFile, { - onInit: (vmi) => (vmi.numberProperty('health')!.value = 20), + onInit: (vmi) => vmi.numberProperty('health')!.set(20), }); return ( @@ -253,7 +253,7 @@ This guide documents how to get started using the Rive React Native runtime. The ); const { riveViewRef, setHybridRef } = useRive(); const { instance: viewModelInstance } = useViewModelInstance(riveFile, { - onInit: (vmi) => (vmi.numberProperty('health')!.value = 20), + onInit: (vmi) => vmi.numberProperty('health')!.set(20), }); const { value: health, setValue: setHealth } = useRiveNumber( @@ -396,7 +396,7 @@ This guide documents how to get started using the Rive React Native runtime. The // With onInit to set initial values synchronously const { instance } = useViewModelInstance(riveFile, { onInit: (vmi) => { - vmi.numberProperty('health')!.value = 100; + vmi.numberProperty('health')!.set(100); }, }); ``` From a4ab76999ff6fe2f423123d24d3beb4bd0a8e018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Thu, 28 May 2026 08:47:00 +0200 Subject: [PATCH 5/5] docs(react-native): add list mutation async methods to migration guide --- runtimes/react-native/migration-guide.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runtimes/react-native/migration-guide.mdx b/runtimes/react-native/migration-guide.mdx index 2bdf9881..0660774a 100644 --- a/runtimes/react-native/migration-guide.mdx +++ b/runtimes/react-native/migration-guide.mdx @@ -292,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)` |