Skip to content

Commit 4cc916d

Browse files
committed
fix!: useRiveProperty hooks start undefined, value delivered via listener
1 parent abdb71e commit 4cc916d

2 files changed

Lines changed: 17 additions & 16 deletions

File tree

src/hooks/__tests__/useRiveProperty.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ describe('useRiveProperty', () => {
1717
},
1818
addListener: jest.fn((callback: (value: string) => void) => {
1919
listener = callback;
20+
// Emit the current value immediately on subscribe, matching native behaviour:
21+
// iOS legacy emits synchronously; experimental backend emits via valueStream.
22+
callback(currentValue);
2023
return () => {
2124
listener = null;
2225
};
@@ -36,7 +39,9 @@ describe('useRiveProperty', () => {
3639
} as unknown as ViewModelInstance;
3740
};
3841

39-
it('should return initial value from property on first render', () => {
42+
it('should return initial value delivered via listener (not from a sync read)', () => {
43+
// Hooks always start undefined; the listener emits the current value immediately
44+
// on subscribe (synchronously for legacy, via stream for experimental).
4045
const mockProperty = createMockProperty('Tea');
4146
const mockInstance = createMockViewModelInstance({
4247
'favDrink/type': mockProperty,
@@ -48,6 +53,8 @@ describe('useRiveProperty', () => {
4853
})
4954
);
5055

56+
// The mock's addListener emits 'Tea' synchronously — React batches it with the
57+
// effect, so the value is available after renderHook (which wraps in act()).
5158
const [value] = result.current;
5259
expect(value).toBe('Tea');
5360
});

src/hooks/useRiveProperty.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export function useRiveProperty<P extends ViewModelProperty, T>(
3434
Error | null,
3535
P | undefined,
3636
] {
37-
// Get the property first so we can read its initial value
3837
const property = useMemo(() => {
3938
if (!viewModelInstance) return;
4039
return options.getProperty(
@@ -43,17 +42,12 @@ export function useRiveProperty<P extends ViewModelProperty, T>(
4342
) as unknown as ObservableViewModelProperty<T>;
4443
}, [options, viewModelInstance, path]);
4544

46-
// Initialize state with property's current value (if available)
47-
const [value, setValue] = useState<T | undefined>(() => property?.value);
45+
// Always start undefined — the listener delivers the current value as its first emission.
46+
// (iOS experimental: via valueStream; iOS/Android legacy: emitted synchronously on subscribe)
47+
// This ensures consumers handle the loading state correctly on all backends.
48+
const [value, setValue] = useState<T | undefined>(undefined);
4849
const [error, setError] = useState<Error | null>(null);
4950

50-
// Sync value when property reference changes (path or instance changed)
51-
useEffect(() => {
52-
if (property) {
53-
setValue(property.value);
54-
}
55-
}, [property]);
56-
5751
// Clear error when path or instance changes
5852
useEffect(() => {
5953
setError(null);
@@ -86,22 +80,22 @@ export function useRiveProperty<P extends ViewModelProperty, T>(
8680
};
8781
}, [options, property]);
8882

89-
// Set the value of the property (no-op if property isn't available yet)
83+
// Set the value of the property (no-op if property isn't available yet).
84+
// Uses tracked `value` from state for updater functions — avoids a synchronous
85+
// property.value read and is consistent with how React state works.
9086
const setPropertyValue = useCallback(
9187
(valueOrUpdater: T | ((prevValue: T | undefined) => T)) => {
9288
if (!property) {
9389
return;
9490
} else {
9591
const newValue =
9692
typeof valueOrUpdater === 'function'
97-
? (valueOrUpdater as (prevValue: T | undefined) => T)(
98-
property.value
99-
)
93+
? (valueOrUpdater as (prevValue: T | undefined) => T)(value)
10094
: valueOrUpdater;
10195
property.value = newValue;
10296
}
10397
},
104-
[property]
98+
[property, value]
10599
);
106100

107101
return [value, setPropertyValue, error, property as unknown as P];

0 commit comments

Comments
 (0)