Skip to content

Commit 2f0f278

Browse files
committed
refactor: useRiveX hooks
1 parent d7954e8 commit 2f0f278

4 files changed

Lines changed: 128 additions & 125 deletions

File tree

example/app/(examples)/DataBinding.tsx

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import {
33
SafeAreaView,
44
ScrollView,
@@ -8,61 +8,67 @@ import {
88
} from 'react-native';
99
import Rive, {
1010
Fit,
11-
RiveRef,
11+
useRive,
1212
useRiveColor,
1313
useRiveNumber,
1414
useRiveString,
1515
} from 'rive-react-native';
1616

1717
export default function DataBinding() {
18-
const riveRef = React.useRef<RiveRef>(null);
18+
const [setRiveRef, riveRef] = useRive();
19+
const [showRive, setShowRive] = useState(false);
1920

2021
let [buttonText, setButtonText] = useRiveString(riveRef, 'Button/State_1');
2122
let [lives, setLives] = useRiveNumber(riveRef, 'Energy_Bar/Lives');
2223
let [barColor, setBarColor] = useRiveColor(riveRef, 'Energy_Bar/Bar_Color');
2324

2425
useEffect(() => {
25-
// Set initial values through hooks
26-
setButtonText("Let's go!");
27-
setLives(7);
28-
setBarColor({ r: 0, g: 255, b: 0, a: 255 });
29-
}, [setButtonText, setLives, setBarColor]);
26+
if (riveRef) {
27+
setButtonText('Hello 123!');
28+
setLives(3);
29+
setBarColor({ r: 0, g: 255, b: 0, a: 255 });
30+
// setBarColor('#00FF00FF'); // Example of using hex color
31+
}
32+
}, [riveRef, setBarColor, setButtonText, setLives]);
33+
34+
// Used for testing that the Rive instance is mounted
35+
// and the properties listeners are being updated correctly
36+
useEffect(() => {
37+
setTimeout(() => setShowRive(true), 1000); // Delay mounting <Rive />
38+
}, []);
3039

3140
console.log('Button Text:', buttonText);
3241
console.log('Lives:', lives);
3342
console.log('Bar Color:', barColor);
3443

35-
// Set values directly
36-
const updateDataBindingValues = () => {
37-
// SET NUMBER VALUE
38-
riveRef.current?.setNumber('Energy_Bar/Lives', 6);
39-
// SET STRING VALUE
40-
riveRef.current?.setString('Button/State_1', 'Direct!');
41-
// SET COLOR VALUE
42-
riveRef.current?.setColor('Energy_Bar/Bar_Color', {
43-
r: 0,
44-
g: 0,
45-
b: 255,
46-
a: 255,
47-
});
48-
// Or set the color using a hex string
49-
// riveRef.current?.setColor('Energy_Bar/Bar_Color', '#0000FFFF');
50-
};
51-
5244
return (
5345
<SafeAreaView style={styles.safeAreaViewContainer}>
5446
<ScrollView contentContainerStyle={styles.container}>
55-
<Rive
56-
ref={riveRef}
57-
fit={Fit.Layout}
58-
style={styles.animation}
59-
layoutScaleFactor={1}
60-
autoplay={true}
61-
stateMachineName={'State Machine 1'}
62-
resourceName={'rewards'}
63-
/>
47+
{showRive && (
48+
<Rive
49+
ref={setRiveRef}
50+
fit={Fit.Layout}
51+
style={styles.animation}
52+
layoutScaleFactor={1}
53+
autoplay={true}
54+
stateMachineName={'State Machine 1'}
55+
resourceName={'rewards'}
56+
/>
57+
)}
6458
<View style={styles.buttonContainer}>
65-
<Button title="Update data" onPress={updateDataBindingValues} />
59+
<Button
60+
title="Update data"
61+
onPress={() => {
62+
riveRef?.setString('Button/State_1', 'Hello!');
63+
riveRef?.setNumber('Energy_Bar/Lives', 3);
64+
riveRef?.setColor('Energy_Bar/Bar_Color', {
65+
r: 255,
66+
g: 0,
67+
b: 0,
68+
a: 255,
69+
});
70+
}}
71+
/>
6672
</View>
6773
</ScrollView>
6874
</SafeAreaView>

src/Rive.tsx

Lines changed: 74 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable no-bitwise */
21
import React, {
32
useCallback,
43
useEffect,
@@ -40,6 +39,7 @@ import { convertErrorFromNativeToRN, XOR } from './helpers';
4039
import { Alignment, Fit } from './types';
4140
import {
4241
getPropertyTypeString,
42+
intToRiveRGBA,
4343
parseColor,
4444
parsePossibleSources,
4545
} from './utils';
@@ -132,153 +132,138 @@ export class RivePropertyValueEmitter {
132132
}
133133
}
134134

135+
export function useRive(): [(node: RiveRef) => void, RiveRef | null] {
136+
const [ref, setRef] = useState<RiveRef | null>(null);
137+
const setRiveRef = useCallback<(node: RiveRef) => void>(
138+
(node) => setRef(node),
139+
[]
140+
);
141+
return [setRiveRef, ref];
142+
}
143+
135144
export function useRiveBoolean(
136-
riveRef: React.RefObject<RiveRef>,
145+
riveRef: RiveRef | null,
137146
path: string
138147
): [boolean | undefined, (value: boolean) => void] {
139148
return useRivePropertyListener<boolean>(riveRef, path, PropertyType.Boolean);
140149
}
141150

142151
export function useRiveString(
143-
riveRef: React.RefObject<RiveRef>,
152+
riveRef: RiveRef | null,
144153
path: string
145154
): [string | undefined, (value: string) => void] {
146155
return useRivePropertyListener<string>(riveRef, path, PropertyType.String);
147156
}
148157

149158
export function useRiveNumber(
150-
riveRef: React.RefObject<RiveRef>,
159+
riveRef: RiveRef | null,
151160
path: string
152161
): [number | undefined, (value: number) => void] {
153162
return useRivePropertyListener<number>(riveRef, path, PropertyType.Number);
154163
}
155164

156165
export function useRiveEnum(
157-
riveRef: React.RefObject<RiveRef>,
166+
riveRef: RiveRef | null,
158167
path: string
159168
): [string | undefined, (value: string) => void] {
160169
return useRivePropertyListener<string>(riveRef, path, PropertyType.Enum);
161170
}
162171

163172
export function useRiveColor(
164-
riveRef: React.RefObject<RiveRef>,
173+
riveRef: RiveRef | null,
165174
path: string
166-
): [RiveRGBA | undefined, (value: RiveRGBA) => void] {
167-
const [color, setColor] = useState<RiveRGBA | undefined>(undefined);
168-
169-
// Convert integer to RiveRGBA
170-
const intToRgba = useCallback((colorValue: number): RiveRGBA => {
171-
const a = (colorValue >> 24) & 0xff;
172-
const r = (colorValue >> 16) & 0xff;
173-
const g = (colorValue >> 8) & 0xff;
174-
const b = colorValue & 0xff;
175-
return { r, g, b, a };
176-
}, []);
177-
178-
// Listener callback to update state
179-
const listenerCallback = useCallback(
180-
(newValue: number) => {
181-
setColor(intToRgba(newValue));
182-
},
183-
[intToRgba]
184-
);
185-
186-
useEffect(() => {
187-
const ref = riveRef.current;
188-
if (!ref) {
189-
return undefined;
190-
}
191-
192-
const listener = ref.internalPropertyListener();
193-
if (!listener) {
194-
return undefined;
195-
}
196-
197-
listener.addListener<number>(path, PropertyType.Color, listenerCallback);
198-
199-
return () => {
200-
listener.removeListener(path, PropertyType.Color, listenerCallback);
201-
};
202-
// eslint-disable-next-line react-hooks/exhaustive-deps
203-
}, [path, listenerCallback]);
204-
205-
const setColorPropertyValue = useCallback(
206-
(newColor: RiveRGBA) => {
207-
const ref = riveRef.current;
208-
if (!ref) {
209-
console.warn('Rive ref is not available to set color property.');
210-
return;
211-
}
212-
ref.setColor(path, newColor);
213-
},
214-
// eslint-disable-next-line react-hooks/exhaustive-deps
215-
[path]
216-
);
217-
218-
return [color, setColorPropertyValue];
175+
): [RiveRGBA | undefined, (value: RiveRGBA | string) => void] {
176+
return useRivePropertyListener<RiveRGBA>(riveRef, path, PropertyType.Color);
219177
}
220178

221179
function useRivePropertyListener<T>(
222-
riveRef: React.RefObject<RiveRef>,
180+
riveRef: RiveRef | null,
223181
path: string,
224182
propertyType: PropertyType
225183
): [T | undefined, (value: T) => void] {
226184
const [value, setValue] = useState<T | undefined>(undefined);
227185

228-
// Listener callback to update state
186+
// Listener callback to update state for non-color properties
229187
const listenerCallback = useCallback((newValue: T) => {
230188
setValue(newValue);
231189
}, []);
232190

233-
useEffect(() => {
234-
const ref = riveRef.current;
235-
if (!ref) {
236-
return undefined;
237-
}
191+
// Listener callback to update state for color properties
192+
const listenerCallbackWithColor = useCallback((newValue: number) => {
193+
const rgbaValue = intToRiveRGBA(newValue);
194+
setValue(rgbaValue as T);
195+
}, []);
238196

239-
const listener = ref.internalPropertyListener();
240-
if (!listener) {
241-
return undefined;
197+
useEffect(() => {
198+
const listener = riveRef?.internalPropertyListener?.();
199+
if (!listener) return () => {};
200+
201+
if (propertyType === PropertyType.Color) {
202+
listener.addListener<number>(
203+
path,
204+
propertyType,
205+
listenerCallbackWithColor
206+
);
207+
return () => {
208+
listener.removeListener<number>(
209+
path,
210+
propertyType,
211+
listenerCallbackWithColor
212+
);
213+
};
214+
} else {
215+
listener.addListener<T>(path, propertyType, listenerCallback);
216+
return () => {
217+
listener.removeListener<T>(path, propertyType, listenerCallback);
218+
};
242219
}
243-
244-
listener.addListener<T>(path, propertyType, listenerCallback);
245-
246-
return () => {
247-
listener.removeListener(path, propertyType, listenerCallback);
248-
};
249-
// eslint-disable-next-line react-hooks/exhaustive-deps
250-
}, [path, propertyType, listenerCallback]);
220+
}, [
221+
riveRef,
222+
path,
223+
propertyType,
224+
listenerCallback,
225+
listenerCallbackWithColor,
226+
]);
251227

252228
// Setter function
253229
const setPropertyValue = useCallback(
254230
(newValue: T) => {
255-
const ref = riveRef.current;
256-
if (!ref) {
257-
console.warn('Rive ref is not available to set property.');
231+
if (!riveRef) {
232+
if (__DEV__) {
233+
console.warn(
234+
`[Rive] Tried to set property "${path}" before riveRef was available.`
235+
);
236+
}
258237
return;
259238
}
260239

261240
switch (propertyType) {
262241
case PropertyType.Number:
263-
ref.setNumber(path, newValue as number);
242+
riveRef.setNumber(path, newValue as number);
264243
break;
265244
case PropertyType.Boolean:
266-
ref.setBoolean(path, newValue as boolean);
245+
riveRef.setBoolean(path, newValue as boolean);
267246
break;
268247
case PropertyType.String:
269-
ref.setString(path, newValue as string);
248+
riveRef.setString(path, newValue as string);
270249
break;
271250
case PropertyType.Enum:
272-
ref.setEnum(path, newValue as string);
251+
riveRef.setEnum(path, newValue as string);
252+
break;
253+
case PropertyType.Color:
254+
const parsedColor =
255+
typeof newValue === 'string' ? parseColor(newValue) : newValue;
256+
riveRef.setColor(path, parsedColor as RiveRGBA);
273257
break;
274258
default:
275-
console.warn(
276-
`Rive unsupported property type in generic listener: ${propertyType}`
277-
);
259+
if (__DEV__) {
260+
console.warn(
261+
`[Rive] Unsupported property type in generic listener: ${propertyType}`
262+
);
263+
}
278264
}
279265
},
280-
// eslint-disable-next-line react-hooks/exhaustive-deps
281-
[path, propertyType]
266+
[riveRef, path, propertyType]
282267
);
283268

284269
return [value, setPropertyValue];

src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './types';
22
import Rive, {
33
RiveRenderer,
4+
useRive,
45
useRiveString,
56
useRiveNumber,
67
useRiveBoolean,
@@ -10,6 +11,7 @@ import Rive, {
1011

1112
export {
1213
RiveRenderer,
14+
useRive,
1315
useRiveString,
1416
useRiveNumber,
1517
useRiveBoolean,

src/utils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-bitwise */
12
import { Image } from 'react-native';
23
import type {
34
FileAssetSource,
@@ -58,7 +59,16 @@ function parseColor(color: string): RiveRGBA {
5859

5960
return { r, g, b, a };
6061
}
61-
export { parsePossibleSources, parseColor };
62+
63+
function intToRiveRGBA(colorValue: number): RiveRGBA {
64+
const a = (colorValue >> 24) & 0xff;
65+
const r = (colorValue >> 16) & 0xff;
66+
const g = (colorValue >> 8) & 0xff;
67+
const b = colorValue & 0xff;
68+
return { r, g, b, a };
69+
}
70+
71+
export { parsePossibleSources, parseColor, intToRiveRGBA };
6272

6373
export const getPropertyTypeString = (propertyType: PropertyType): string =>
6474
propertyType;

0 commit comments

Comments
 (0)