Skip to content

Commit 155776e

Browse files
committed
fix!: use discriminated union with Error type for useViewModelInstance result
1 parent 6c47e11 commit 155776e

6 files changed

Lines changed: 32 additions & 18 deletions

File tree

example/src/demos/DataBindingArtboardsExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function ArtboardSwapper({
7878
mainFile: RiveFile;
7979
assetsFile: RiveFile;
8080
}) {
81-
const instance = useViewModelInstance(mainFile);
81+
const { instance } = useViewModelInstance(mainFile);
8282
const [currentArtboard, setCurrentArtboard] = useState<string>('Dragon');
8383
const initializedRef = useRef(false);
8484

example/src/exercisers/FontFallbackExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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 = useViewModelInstance(riveFile ?? null);
261+
const { instance } = useViewModelInstance(riveFile ?? null);
262262

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

example/src/exercisers/NestedViewModelExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default function NestedViewModelExample() {
3838
}
3939

4040
function WithViewModelSetup({ file }: { file: RiveFile }) {
41-
const instance = useViewModelInstance(file);
41+
const { instance } = useViewModelInstance(file);
4242

4343
if (!instance) {
4444
return <ActivityIndicator size="large" color="#0000ff" />;

example/src/exercisers/RiveDataBindingExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default function WithRiveFile() {
3535
}
3636

3737
function WithViewModelSetup({ file }: { file: RiveFile }) {
38-
const instance = useViewModelInstance(file);
38+
const { instance } = useViewModelInstance(file);
3939

4040
if (!instance) {
4141
return <ActivityIndicator size="large" color="#0000ff" />;

src/hooks/__tests__/useViewModelInstance.test.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('useViewModelInstance - RiveFile with instanceName parameter', () => {
107107
expect(result.current.error).toBeNull();
108108
});
109109

110-
it('should return null when instance name not found and required is false', () => {
110+
it('should return error when instance name not found and required is false', () => {
111111
const defaultViewModel = createMockViewModel({
112112
namedInstances: {},
113113
});
@@ -119,7 +119,8 @@ describe('useViewModelInstance - RiveFile with instanceName parameter', () => {
119119
);
120120

121121
expect(result.current.instance).toBeNull();
122-
expect(result.current.error).toContain('not found');
122+
expect(result.current.error).toBeInstanceOf(Error);
123+
expect(result.current.error?.message).toContain('NonExistent');
123124
});
124125

125126
it('should throw when instance name not found and required is true', () => {
@@ -139,7 +140,7 @@ describe('useViewModelInstance - RiveFile with instanceName parameter', () => {
139140
).toThrow("ViewModel instance 'NonExistent' not found");
140141
});
141142

142-
it('should return null when artboardName not found and required is false', () => {
143+
it('should return error when artboardName not found and required is false', () => {
143144
const mockRiveFile = createMockRiveFile({
144145
artboardViewModels: {},
145146
});
@@ -149,7 +150,8 @@ describe('useViewModelInstance - RiveFile with instanceName parameter', () => {
149150
);
150151

151152
expect(result.current.instance).toBeNull();
152-
expect(result.current.error).toContain('not found');
153+
expect(result.current.error).toBeInstanceOf(Error);
154+
expect(result.current.error?.message).toContain('MissingArtboard');
153155
});
154156

155157
it('should throw when artboardName not found and required is true', () => {
@@ -262,7 +264,7 @@ describe('useViewModelInstance - RiveFile with viewModelName parameter', () => {
262264
expect(result.current.error).toBeNull();
263265
});
264266

265-
it('should return null when viewModelName not found and required is false', () => {
267+
it('should return error when viewModelName not found and required is false', () => {
266268
const mockRiveFile = createMockRiveFile({
267269
namedViewModels: {},
268270
});
@@ -272,7 +274,8 @@ describe('useViewModelInstance - RiveFile with viewModelName parameter', () => {
272274
);
273275

274276
expect(result.current.instance).toBeNull();
275-
expect(result.current.error).toContain('not found');
277+
expect(result.current.error).toBeInstanceOf(Error);
278+
expect(result.current.error?.message).toContain('NonExistent');
276279
});
277280

278281
it('should throw when viewModelName not found and required is true', () => {

src/hooks/useViewModelInstance.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,17 +176,17 @@ function createInstance(
176176
return { instance: vmi ?? null, needsDispose: true };
177177
}
178178

179-
export type UseViewModelInstanceResult = {
180-
instance: ViewModelInstance | null;
181-
error: string | null;
182-
};
179+
export type UseViewModelInstanceResult =
180+
| { instance: ViewModelInstance; error: null }
181+
| { instance: null; error: Error }
182+
| { instance: null; error: null };
183183

184184
/**
185185
* Hook for getting a ViewModelInstance from a RiveFile, ViewModel, or RiveViewRef.
186186
*
187187
* @param source - The RiveFile, ViewModel, or RiveViewRef to get an instance from
188188
* @param params - Configuration for which instance to retrieve
189-
* @returns An object with `instance` (the ViewModelInstance or null) and `error` (string or null)
189+
* @returns An object with `instance` and `error` (discriminated union)
190190
*
191191
* @example
192192
* ```tsx
@@ -234,7 +234,7 @@ export type UseViewModelInstanceResult = {
234234
* ```tsx
235235
* // Create a new blank instance from ViewModel
236236
* const viewModel = file.viewModelByName('TodoItem');
237-
* const { instance: newInstance } = useViewModelInstance(viewModel, { useNew: true });
237+
* const { instance } = useViewModelInstance(viewModel, { useNew: true });
238238
* ```
239239
*
240240
* @example
@@ -253,7 +253,13 @@ export type UseViewModelInstanceResult = {
253253
* vmi.stringProperty('name').set(userName);
254254
* }
255255
* });
256-
* // Values are already set here
256+
* ```
257+
*
258+
* @example
259+
* ```tsx
260+
* // Error handling
261+
* const { instance, error } = useViewModelInstance(riveFile, { viewModelName: 'Missing' });
262+
* if (error) console.error(error.message);
257263
* ```
258264
*/
259265
// RiveFile overloads
@@ -352,6 +358,8 @@ export function useViewModelInstance(
352358
};
353359
}, []);
354360

361+
const error = result.error ? new Error(result.error) : null;
362+
355363
if (required && result.instance === null) {
356364
throw new Error(
357365
result.error
@@ -361,5 +369,8 @@ export function useViewModelInstance(
361369
);
362370
}
363371

364-
return { instance: result.instance, error: result.error ?? null };
372+
if (result.instance) {
373+
return { instance: result.instance, error: null };
374+
}
375+
return { instance: null, error };
365376
}

0 commit comments

Comments
 (0)