> ## Documentation Index
> Fetch the complete documentation index at: https://rive.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migration Guide

> 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 theme={null}
    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 theme={null}
    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 theme={null}
    const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv'));

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

  <Tab title="Previous API">
    ```tsx theme={null}
    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 theme={null}
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).

### What's Changed

* **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
* **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

#### 1. ViewModel Access

<Tabs>
  <Tab title="New API">
    ```tsx theme={null}
    const names = await file.getViewModelNamesAsync();
    const vm = await file.viewModelByNameAsync('Person');
    const defaultVM = await file.defaultArtboardViewModelAsync();
    ```
  </Tab>

  <Tab title="Deprecated API">
    ```tsx theme={null}
    const count = file.viewModelCount;
    const vm = file.viewModelByName('Person');
    const defaultVM = file.defaultArtboardViewModel();
    ```
  </Tab>
</Tabs>

#### 2. Instance Creation

<Tabs>
  <Tab title="New API">
    ```tsx theme={null}
    const instance = await vm.createDefaultInstanceAsync();
    const named = await vm.createInstanceByNameAsync('player1');
    const blank = await vm.createBlankInstanceAsync();
    ```
  </Tab>

  <Tab title="Deprecated API">
    ```tsx theme={null}
    const instance = vm.createDefaultInstance();
    const named = vm.createInstanceByName('player1');
    const blank = vm.createInstance();
    ```
  </Tab>
</Tabs>

<Tip>
  The `useViewModelInstance` hook handles ViewModel resolution and instance
  creation for you — in most cases you don't need to call these methods
  directly.
</Tip>

#### 3. Property Value Access

<Tabs>
  <Tab title="New API">
    ```tsx theme={null}
    const num = await prop.getValueAsync();
    prop.set(42);
    ```
  </Tab>

  <Tab title="Deprecated API">
    ```tsx theme={null}
    const num = prop.value;
    prop.value = 42;
    ```
  </Tab>
</Tabs>

#### 4. Nested ViewModelInstance Access

<Tabs>
  <Tab title="New API">
    ```tsx theme={null}
    const nested = await instance.viewModelAsync('Header');
    ```
  </Tab>

  <Tab title="Deprecated API">
    ```tsx theme={null}
    const nested = instance.viewModel('Header');
    ```
  </Tab>
</Tabs>

#### 5. List Property Access

<Tabs>
  <Tab title="New API">
    ```tsx theme={null}
    const len = await listProp.getLengthAsync();
    const item = await listProp.getInstanceAtAsync(0);
    ```
  </Tab>

  <Tab title="Deprecated API">
    ```tsx theme={null}
    const len = listProp.length;
    const item = listProp.getInstanceAt(0);
    ```
  </Tab>
</Tabs>

#### 6. Artboard Access

<Tabs>
  <Tab title="New API">
    ```tsx theme={null}
    const count = await file.getArtboardCountAsync();
    const names = await file.getArtboardNamesAsync();
    ```
  </Tab>

  <Tab title="Deprecated API">
    ```tsx theme={null}
    const count = file.artboardCount;
    const names = file.artboardNames;
    ```
  </Tab>
</Tabs>

#### 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 theme={null}
    const { riveFile } = useRiveFile(require('./animation.riv'));
    const { instance } = useViewModelInstance(riveFile);

    const { value: health, setValue: setHealth } = useRiveNumber(
      'health',
      instance
    );

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

  <Tab title="Manual Async Setup">
    ```tsx theme={null}
    const { riveFile } = useRiveFile(require('./animation.riv'));
    const [instance, setInstance] = useState<ViewModelInstance>();

    useEffect(() => {
      if (!riveFile) return;
      let cancelled = false;
      (async () => {
        const vm = await riveFile.defaultArtboardViewModelAsync();
        const vmi = await vm?.createDefaultInstanceAsync();
        if (!cancelled) setInstance(vmi ?? undefined);
      })();
      return () => { cancelled = true; };
    }, [riveFile]);
    ```
  </Tab>

  <Tab title="Deprecated Sync Pattern">
    ```tsx theme={null}
    function Setup({ file }) {
      const vm = useMemo(() => file.defaultArtboardViewModel(), [file]);
      const instance = useMemo(
        () => vm?.createDefaultInstance(),
        [vm]
      );
      if (!instance) return null;
      return <MyComponent instance={instance} file={file} />;
    }
    ```
  </Tab>
</Tabs>

### Quick Reference

| Deprecated                             | Replacement                                                      |
| -------------------------------------- | ---------------------------------------------------------------- |
| `file.viewModelByName(name)`           | `await file.viewModelByNameAsync(name)`                          |
| `file.defaultArtboardViewModel()`      | `await file.defaultArtboardViewModelAsync()`                     |
| `file.viewModelCount`                  | `(await file.getViewModelNamesAsync()).length`                   |
| `file.viewModelByIndex(i)`             | `await file.viewModelByNameAsync(name)`                          |
| `file.artboardCount` / `artboardNames` | `await file.getArtboardCountAsync()` / `getArtboardNamesAsync()` |
| `vm.createDefaultInstance()`           | `await vm.createDefaultInstanceAsync()`                          |
| `vm.createInstanceByName(name)`        | `await vm.createInstanceByNameAsync(name)`                       |
| `vm.createInstance()`                  | `await vm.createBlankInstanceAsync()`                            |
| `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)`                                                    |

### Platform Caveats

| Limitation                                | Details                                                                      |
| ----------------------------------------- | ---------------------------------------------------------------------------- |
| `replaceViewModel()`                      | No-op on Android. Works on iOS.                                              |
| `addListener()` on image/list properties  | No-op on both platforms. Poll with `getLengthAsync()` instead.               |
| `addInstanceAt()` / `swap()` return value | Always `true` on iOS. Android returns correct value.                         |
| `instanceName`                            | Empty string except for instances created via `createInstanceByNameAsync()`. |
| `defaultArtboardViewModel()` on Android   | Uses a heuristic. Prefer `viewModelByNameAsync(name)` with an explicit name. |

## Migrating from `v0.1.0` to `v0.2.0`

This change updates to a new major version of Nitro to resolve a view recycling issue. See the [release](https://github.com/rive-app/rive-nitro-react-native/releases/tag/v0.2.0) for more details.

The only change required is to update the version of Nitro used in your app.

## Migrating from `rive-react-native` to `@rive-app/react-native`

The new Rive React Native runtime (`@rive-app/react-native`) is a complete rewrite built with [Nitro Modules](https://nitro.margelo.com/) for improved performance and better React Native integration.

<Note>
  All your Rive graphics will still look and function the same as they did
  before.
</Note>

### What's New

**RiveFile Ownership:** You now own the `RiveFile` object via the `useRiveFile` hook, enabling file caching, multiple instances from one file, and better resource management.

```tsx theme={null}
// Old: file managed internally
<Rive url="https://cdn.rive.app/animations/vehicles.riv" />

// New: you own the RiveFile
const { riveFile } = useRiveFile(require("./vehicles.riv"));
<RiveView file={riveFile} />
```

**Enhanced Data Binding:** Direct access to `ViewModel` and `ViewModelInstance` objects, enabling initialization hooks, multiple instances, and support for all property types (lists, images, artboards).

```tsx theme={null}
// Old: hooks take riveRef, return tuples
const [setRiveRef, riveRef] = useRive();
const [health, setHealth] = useRiveNumber(riveRef, "health");

// New: hooks take viewModelInstance, return objects
const { instance: viewModelInstance } = useViewModelInstance(riveFile);
const { value: health, setValue: setHealth } = useRiveNumber(
  "health",
  viewModelInstance
);
```

**Improved Error Handling:**

Error handling is improved. See the [error handling](/runtimes/react-native/error-handling) documentation for more information.

### Requirements

* **React Native**: 0.78+ (0.79+ recommended)
* **Expo SDK**: 53+ (for Expo users)
* **iOS**: 15.1+
* **Android**: SDK 24+
* **Xcode**: 16.4+
* **JDK**: 17+
* **Nitro Modules**: 0.25.2+

### Migration Steps

#### 1. Installation

```bash theme={null}
npm uninstall rive-react-native
npm install @rive-app/react-native react-native-nitro-modules
```

<Note>
  `react-native-nitro-modules` is required as this library relies on [Nitro
  Modules](https://nitro.margelo.com/).
</Note>

#### 2. Update Imports

```tsx theme={null}
// Old
import Rive from "rive-react-native";

// New
import { RiveView } from "@rive-app/react-native";
```

#### 3. Loading Rive Files

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    const { riveFile, isLoading, error } = useRiveFile(require('./animation.riv'));
    // Also supports: URL string, resource name, or ArrayBuffer

    return <RiveView file={riveFile} style={{ width: 400, height: 400 }} />;
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    <Rive source={require('./animation.riv')} />
    // Also supports: url or resourceName props
    ```
  </Tab>
</Tabs>

See [loading Rive files](/runtimes/react-native/loading-rive-files) for more information.

#### 4. Component Migration

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    <RiveView
      file={riveFile}
      artboardName="MainArtboard"
      stateMachineName="State Machine 1"
      fit={Fit.Contain}
      autoPlay={true}
      style={{ width: 400, height: 400 }}
    />
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    <Rive
      url="https://cdn.rive.app/animations/vehicles.riv"
      artboardName="MainArtboard"
      stateMachineName="State Machine 1"
      fit={Fit.Contain}
      autoplay={true}
      style={{ width: 400, height: 400 }}
    />
    ```
  </Tab>
</Tabs>

See [React Native runtime](/runtimes/react-native) and [props](/runtimes/react-native/props) documentation for more information.

#### 5. View Reference Migration

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    const { riveViewRef, setHybridRef } = useRive();

    riveViewRef?.play();
    riveViewRef?.pause();

    <RiveView hybridRef={setHybridRef} file={riveFile} />
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    const [setRiveRef, riveRef] = useRive();

    riveRef?.play();
    riveRef?.pause();

    <Rive ref={setRiveRef} url="..." />
    ```
  </Tab>
</Tabs>

See [Rive ref methods](/runtimes/react-native/rive-ref-methods) for more information.

#### 6. State Machine Inputs (Deprecated)

<Warning>
  These methods are deprecated. Migrate to [data binding](#8-data-binding)
  instead.
</Warning>

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    riveViewRef?.setNumberInputValue('level', 5);
    riveViewRef?.setBooleanInputValue('isActive', true);
    riveViewRef?.triggerInput('buttonPressed');
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    riveRef.current?.setInputState('State Machine 1', 'level', 5);
    riveRef.current?.setInputState('State Machine 1', 'isActive', true);
    riveRef.current?.fireState('State Machine 1', 'buttonPressed');
    ```
  </Tab>
</Tabs>

See [state machine inputs](/runtimes/react-native/inputs) for more information.

#### 7. Rive Events (Deprecated)

<Warning>
  These methods are deprecated. Use [data binding triggers](#8-data-binding)
  instead.
</Warning>

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    useEffect(() => {
      const handleEvent = (event: RiveEvent) => console.log(event);
      riveViewRef?.onEventListener(handleEvent);
      return () => riveViewRef?.removeEventListeners();
    }, [riveViewRef]);
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    <Rive
      onRiveEventReceived={(event) => console.log(event)}
      url="..."
    />
    ```
  </Tab>
</Tabs>

See [runtime events](/runtimes/react-native/rive-events) for more information.

#### 8. Data Binding

The new runtime significantly improves data binding by giving you direct access to `ViewModelInstance` objects. This enables:

* **Initialization hooks** - Set initial property values before rendering with `onInit` callback
* **Multiple instances** - Create and manage multiple view model instances from the same file
* **Advanced property types** - Full support for lists, images, artboards, and nested view models
* **Better React integration** - Property hooks integrate seamlessly with React state and lifecycle

Main API changes:

* Property hooks take `viewModelInstance` instead of `riveRef`
* Hooks return objects instead of tuples
* Parameters are swapped: `(path, viewModelInstance)` vs `(riveRef, path)`

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    const { riveFile } = useRiveFile(require('./animation.riv'));
    const { instance: viewModelInstance } = useViewModelInstance(riveFile);

    const { value: health, setValue: setHealth } = useRiveNumber('health', viewModelInstance);
    const { value: name, setValue: setName } = useRiveString('Player/Name', viewModelInstance);
    const { trigger } = useRiveTrigger('gameOver', viewModelInstance, {
      onTrigger: () => console.log('Game Over!')
    });

    <RiveView file={riveFile} dataBind={viewModelInstance} />
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    const [setRiveRef, riveRef] = useRive();

    const [health, setHealth] = useRiveNumber(riveRef, 'health');
    const [name, setName] = useRiveString(riveRef, 'Player/Name');
    useRiveTrigger(riveRef, 'gameOver', () => console.log('Game Over!'));

    <Rive ref={setRiveRef} resourceName="animation" dataBinding={AutoBind(true)} />
    ```
  </Tab>
</Tabs>

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

#### 9. Out of Band Assets

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    const { riveFile } = useRiveFile(
      require('./animation.riv'),
      {
        referencedAssets: {
          'Inter-594377': {
            source: require('./fonts/Inter-594377.ttf'),
          },
          'my-image': {
            source: { uri: 'https://example.com/image.png' },
          },
        },
      }
    );
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    <Rive
      url="..."
      referencedAssets={{
        'Inter-594377': {
          source: require('./fonts/Inter-594377.ttf'),
        },
      }}
    />
    ```
  </Tab>
</Tabs>

See [loading assets](/runtimes/react-native/loading-assets) for more information.

#### 10. Text Run Updates (Deprecated)

<Warning>
  Direct text run methods (`.setTextRunValue()`, `.getTextRunValue()`) are
  **deprecated**. Migrate to [data binding](#8-data-binding) strings instead.
</Warning>

<Tabs>
  <Tab title="Data Binding (Recommended)">
    ```tsx theme={null}
    const { value: playerName, setValue: setPlayerName } = useRiveString('playerName', viewModelInstance);
    setPlayerName('John Doe');
    ```
  </Tab>

  <Tab title="New Runtime (Deprecated)">
    ```tsx theme={null}
    // The new runtime still supports text run methods like the old runtime
    riveViewRef?.setTextRunValue('playerName', 'John Doe');
    const name = riveViewRef?.getTextRunValue('playerName');
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    riveRef.current?.setTextRunValue('playerName', 'John Doe');
    ```
  </Tab>
</Tabs>

See [text runs](/runtimes/react-native/text) for more information.

#### 11. Callbacks

<Tabs>
  <Tab title="New Runtime">
    ```tsx theme={null}
    <RiveView
      file={riveFile}
      onError={(error) => console.error('Rive error:', error)}
    />

    // For state changes, use data binding listeners. For example, a trigger:
    const { trigger } = useRiveTrigger('onStateChange', viewModelInstance, {
      onTrigger: () => console.log('State changed')
    });
    ```
  </Tab>

  <Tab title="Legacy Runtime">
    ```tsx theme={null}
    <Rive
      onPlay={(name, isSM) => console.log('Playing:', name)}
      onPause={(name, isSM) => console.log('Paused:', name)}
      onStateChanged={(sm, state) => console.log('State changed:', state)}
      onError={(error) => console.error('Error:', error)}
    />
    ```
  </Tab>
</Tabs>

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

### Getting Help

If you encounter issues:

1. Check the [new runtime documentation](/runtimes/react-native)
2. Review the [Data Binding guide](/runtimes/react-native/data-binding)
3. See the [example app](https://github.com/rive-app/rive-nitro-react-native/tree/main/example)
4. Visit the [Rive community forums](https://community.rive.app)
5. Report issues on [GitHub](https://github.com/rive-app/rive-nitro-react-native/issues)
