Skip to content

Commit 7db22bb

Browse files
committed
feat(hooks)!: fold the async variant into useViewModelInstance({ async: true })
Replace the separate useViewModelInstanceAsync export with an async param on useViewModelInstance, so the API keeps a single name and the migration is a param flip rather than a rename: - overloads with async: true return the async result; the non-async overloads are @deprecated per-overload ('pass async: true') and will flip to async-by-default in the next major - the sync result gains isLoading (true only while the source is pending), which makes the sync and async result unions structurally identical — call sites don't change shape when the default flips - the async implementation is unchanged and stays in useViewModelInstanceAsync.ts as the internal hook behind the flag; the async param must stay constant for a component's lifetime (it selects between hook implementations) - examples, unit tests, and the e2e harness now call useViewModelInstance(source, { async: true, ... }); repro/regression tests that intentionally ride the sync path carry explicit no-deprecated disables
1 parent 1877cc1 commit 7db22bb

17 files changed

Lines changed: 261 additions & 131 deletions

example/__tests__/useViewModelInstanceAsync-e2e.harness.tsx renamed to example/__tests__/useViewModelInstance-async-e2e.harness.tsx

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
RiveView,
1515
useRive,
1616
useRiveFile,
17-
useViewModelInstanceAsync,
17+
useViewModelInstance,
1818
type RiveFile,
1919
type RiveFileInput,
2020
type RiveViewRef,
@@ -58,7 +58,8 @@ function ArtboardProbe({
5858
artboardName: string;
5959
ctx: AsyncCtx;
6060
}) {
61-
const { instance, error, isLoading } = useViewModelInstanceAsync(file, {
61+
const { instance, error, isLoading } = useViewModelInstance(file, {
62+
async: true,
6263
artboardName,
6364
});
6465
useEffect(() => {
@@ -84,7 +85,8 @@ function ViewModelProbe({
8485
onInit?: (vmi: ViewModelInstance) => void;
8586
ctx: AsyncCtx;
8687
}) {
87-
const { instance, error, isLoading } = useViewModelInstanceAsync(file, {
88+
const { instance, error, isLoading } = useViewModelInstance(file, {
89+
async: true,
8890
viewModelName,
8991
onInit,
9092
});
@@ -111,7 +113,8 @@ function InstanceNameProbe({
111113
instanceName: string;
112114
ctx: AsyncCtx;
113115
}) {
114-
const { instance, error, isLoading } = useViewModelInstanceAsync(file, {
116+
const { instance, error, isLoading } = useViewModelInstance(file, {
117+
async: true,
115118
viewModelName,
116119
instanceName,
117120
});
@@ -134,7 +137,9 @@ function FixedSourceProbe({
134137
source: RiveFile | null | undefined;
135138
ctx: AsyncCtx;
136139
}) {
137-
const { instance, error, isLoading } = useViewModelInstanceAsync(source);
140+
const { instance, error, isLoading } = useViewModelInstance(source, {
141+
async: true,
142+
});
138143
useEffect(() => {
139144
ctx.instance = instance;
140145
ctx.error = error;
@@ -157,7 +162,9 @@ function FileErrorProbe({
157162
ctx: FileErrorCtx;
158163
}) {
159164
const { riveFile, error: fileError } = useRiveFile(input);
160-
const { instance, error, isLoading } = useViewModelInstanceAsync(riveFile);
165+
const { instance, error, isLoading } = useViewModelInstance(riveFile, {
166+
async: true,
167+
});
161168
useEffect(() => {
162169
ctx.fileErrored = fileError != null;
163170
ctx.instance = instance;
@@ -176,7 +183,7 @@ function FileErrorProbe({
176183
// while the legacy backend resolves undefined; the hook must map both to the
177184
// same friendly error instead of leaking a raw native message.
178185

179-
describe('useViewModelInstanceAsync: unknown artboard name', () => {
186+
describe('useViewModelInstance async: unknown artboard name', () => {
180187
it('surfaces the friendly "not found" error instead of the raw native message', async () => {
181188
const file = await loadFile();
182189
const ctx = createCtx();
@@ -211,7 +218,7 @@ describe('useViewModelInstanceAsync: unknown artboard name', () => {
211218
// throwing onInit silently blank-screened. These lock the contract the fixed
212219
// example now relies on: a throw becomes `error`, a clean onInit resolves.
213220

214-
describe('useViewModelInstanceAsync: onInit', () => {
221+
describe('useViewModelInstance async: onInit', () => {
215222
it('surfaces an onInit throw as error and leaves instance null', async () => {
216223
const file = await loadFile();
217224
const ctx = createCtx();
@@ -258,7 +265,7 @@ describe('useViewModelInstanceAsync: onInit', () => {
258265
// terminate on `null`, otherwise a consumer keying a spinner off `isLoading`
259266
// hangs forever with no signal.
260267

261-
describe('useViewModelInstanceAsync: null vs undefined source', () => {
268+
describe('useViewModelInstance async: null vs undefined source', () => {
262269
it('stays loading while the source is undefined (file still resolving)', async () => {
263270
const ctx = createCtx();
264271
await render(<FixedSourceProbe source={undefined} ctx={ctx} />);
@@ -299,7 +306,7 @@ describe('useViewModelInstanceAsync: null vs undefined source', () => {
299306
// `contains()` pre-check). Asserting a cause per-platform would pin that
300307
// accidental divergence, so only its shape is checked when present.
301308

302-
describe('useViewModelInstanceAsync: instance creation failure', () => {
309+
describe('useViewModelInstance async: instance creation failure', () => {
303310
it('keeps a clean not-found message on every backend (cause optional)', async () => {
304311
const file = await loadFile();
305312
const ctx = createCtx();
@@ -347,7 +354,9 @@ describe('useViewModelInstanceAsync: instance creation failure', () => {
347354

348355
function RefSourceConsumer({ file, ctx }: { file: RiveFile; ctx: AsyncCtx }) {
349356
const { riveViewRef, setHybridRef } = useRive();
350-
const { instance, error, isLoading } = useViewModelInstanceAsync(riveViewRef);
357+
const { instance, error, isLoading } = useViewModelInstance(riveViewRef, {
358+
async: true,
359+
});
351360
useEffect(() => {
352361
ctx.instance = instance;
353362
ctx.error = error;
@@ -370,7 +379,9 @@ function EagerRefConsumer({ file, ctx }: { file: RiveFile; ctx: AsyncCtx }) {
370379
// Raw hybridRef: the ref is exposed the moment the native view attaches,
371380
// before awaitViewReady/auto-bind complete — the widest race window.
372381
const [viewRef, setViewRef] = useState<RiveViewRef | undefined>(undefined);
373-
const { instance, error, isLoading } = useViewModelInstanceAsync(viewRef);
382+
const { instance, error, isLoading } = useViewModelInstance(viewRef, {
383+
async: true,
384+
});
374385
useEffect(() => {
375386
ctx.instance = instance;
376387
ctx.error = error;
@@ -397,7 +408,7 @@ function EagerRefConsumer({ file, ctx }: { file: RiveFile; ctx: AsyncCtx }) {
397408
// getViewModelInstance() read races the bind and can settle a terminal
398409
// { instance: null } on fast mounts.
399410

400-
describe('useViewModelInstanceAsync: RiveViewRef source', () => {
411+
describe('useViewModelInstance async: RiveViewRef source', () => {
401412
it('resolves the auto-bound instance from a useRive view ref', async () => {
402413
// getViewModelInstance() returns null on Android experimental — auto-bind
403414
// doesn't expose the VMI handle to JS yet (same skip as autoplay.harness).
@@ -438,7 +449,7 @@ describe('useViewModelInstanceAsync: RiveViewRef source', () => {
438449
// natives normalize this to resolve-null so the hook's documented
439450
// { instance: null, error: null } state is reachable on every backend.
440451

441-
describe('useViewModelInstanceAsync: file without a default ViewModel', () => {
452+
describe('useViewModelInstance async: file without a default ViewModel', () => {
442453
it('resolves null with no error', async () => {
443454
const file = await RiveFileFactory.fromSource(NO_DEFAULT_VM, undefined);
444455
const ctx = createCtx();

example/src/__tests__/issue297.hooks.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function AgeHook({ instance, ctx }: { instance: ViewModelInstance; ctx: Ctx }) {
3636
}
3737

3838
function Repro({ file, ctx }: { file: RiveFile; ctx: Ctx }) {
39+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- #297 regression rides the sync creation path on purpose
3940
const { instance } = useViewModelInstance(file);
4041
const [hookKey, setHookKey] = useState(0);
4142
useEffect(() => {

example/src/__tests__/staleSetterWrite.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function AgeHook({ instance, ctx }: { instance: ViewModelInstance; ctx: Ctx }) {
3434
}
3535

3636
function Repro({ file, ctx }: { file: RiveFile; ctx: Ctx }) {
37+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- stale-write regression rides the sync creation path on purpose
3738
const { instance } = useViewModelInstance(file);
3839
if (!instance) return null;
3940
return (

example/src/demos/DataBindingArtboardsExample.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Fit,
1111
RiveView,
1212
useRiveFile,
13-
useViewModelInstanceAsync,
13+
useViewModelInstance,
1414
type RiveFile,
1515
type BindableArtboard,
1616
} from '@rive-app/react-native';
@@ -78,7 +78,7 @@ function ArtboardSwapper({
7878
mainFile: RiveFile;
7979
assetsFile: RiveFile;
8080
}) {
81-
const { instance, error } = useViewModelInstanceAsync(mainFile);
81+
const { instance, error } = useViewModelInstance(mainFile, { async: true });
8282
const [currentArtboard, setCurrentArtboard] = useState<string>('Dragon');
8383
const initializedRef = useRef(false);
8484

example/src/demos/QuickStart.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
useRiveFile,
1414
useRiveNumber,
1515
useRiveTrigger,
16-
useViewModelInstanceAsync,
16+
useViewModelInstance,
1717
Fit,
1818
} from '@rive-app/react-native';
1919
import type { Metadata } from '../shared/metadata';
@@ -23,9 +23,10 @@ export default function QuickStart() {
2323
require('../../assets/rive/quick_start.riv')
2424
);
2525
const { riveViewRef, setHybridRef } = useRive();
26-
const { instance: viewModelInstance, error } = useViewModelInstanceAsync(
26+
const { instance: viewModelInstance, error } = useViewModelInstance(
2727
riveFile,
2828
{
29+
async: true,
2930
onInit: (vmi) => vmi.numberProperty('health')!.set(9),
3031
}
3132
);

example/src/exercisers/FontFallbackExample.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
useRive,
1616
useRiveFile,
1717
useRiveString,
18-
useViewModelInstanceAsync,
18+
useViewModelInstance,
1919
type FontSource,
2020
type FallbackFont,
2121
} from '@rive-app/react-native';
@@ -258,7 +258,7 @@ function MountedView({ text }: { text: string }) {
258258
// https://rive.app/marketplace/26480-49641-simple-test-text-property/
259259
require('../../assets/rive/font_fallback.riv')
260260
);
261-
const { instance } = useViewModelInstanceAsync(riveFile);
261+
const { instance } = useViewModelInstance(riveFile, { async: true });
262262

263263
const { setValue: setRiveText, error: textError } = useRiveString(
264264
TEXT_PROPERTY,

example/src/exercisers/MenuListExample.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
type RiveFile,
1717
useRiveFile,
1818
useRiveList,
19-
useViewModelInstanceAsync,
19+
useViewModelInstance,
2020
} from '@rive-app/react-native';
2121
import { type Metadata } from '../shared/metadata';
2222

@@ -41,7 +41,7 @@ export default function MenuListExample() {
4141
}
4242

4343
function MenuList({ file }: { file: RiveFile }) {
44-
const { instance, error } = useViewModelInstanceAsync(file);
44+
const { instance, error } = useViewModelInstance(file, { async: true });
4545

4646
if (error) {
4747
console.error(error.message);

example/src/exercisers/NestedViewModelExample.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
RiveView,
1414
useRiveFile,
1515
useRiveString,
16-
useViewModelInstanceAsync,
16+
useViewModelInstance,
1717
type ViewModelInstance,
1818
type RiveFile,
1919
type RiveViewRef,
@@ -41,7 +41,7 @@ export default function NestedViewModelExample() {
4141
}
4242

4343
function WithViewModelSetup({ file }: { file: RiveFile }) {
44-
const { instance, error } = useViewModelInstanceAsync(file);
44+
const { instance, error } = useViewModelInstance(file, { async: true });
4545

4646
if (error) {
4747
console.error(error.message);

example/src/exercisers/RiveDataBindingExample.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
Fit,
55
RiveView,
66
useRiveNumber,
7-
useViewModelInstanceAsync,
7+
useViewModelInstance,
88
type ViewModelInstance,
99
type RiveFile,
1010
useRiveString,
@@ -37,7 +37,7 @@ export default function WithRiveFile() {
3737
}
3838

3939
function WithViewModelSetup({ file }: { file: RiveFile }) {
40-
const { instance, error } = useViewModelInstanceAsync(file);
40+
const { instance, error } = useViewModelInstance(file, { async: true });
4141

4242
if (error) {
4343
console.error(error.message);

example/src/reproducers/Issue297ThreadRace.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
Fit,
1212
RiveView,
1313
useRiveFile,
14-
useViewModelInstanceAsync,
14+
useViewModelInstance,
1515
type ViewModelInstance,
1616
type RiveFile,
1717
} from '@rive-app/react-native';
@@ -130,7 +130,7 @@ function StressRunner({
130130
}
131131

132132
function WithViewModelSetup({ file }: { file: RiveFile }) {
133-
const { instance, error } = useViewModelInstanceAsync(file);
133+
const { instance, error } = useViewModelInstance(file, { async: true });
134134

135135
if (error) {
136136
return <Text style={styles.errorText}>{error.message}</Text>;

0 commit comments

Comments
 (0)