Skip to content

Commit 99fb610

Browse files
committed
fix api
1 parent 40a7f48 commit 99fb610

File tree

2 files changed

+139
-95
lines changed

2 files changed

+139
-95
lines changed

src/core/instrumentation/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ export const instrument = ({
140140
onCommitStart();
141141

142142
const handleFiber = (fiber: Fiber, trigger: boolean) => {
143+
// to-do, don't traverse fibers part from react-scan, get all components from no-traverse subtree and put it in a weakset
143144
const type = getType(fiber.type);
144145
if (!type) return null;
145146
if (!didFiberRender(fiber)) return null;

src/core/native/index.tsx

Lines changed: 138 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ import {
1515
Text,
1616
} from '@shopify/react-native-skia';
1717
import React, {
18+
createContext,
19+
useContext,
1820
useEffect,
1921
useRef,
2022
useState,
21-
useSyncExternalStore,
2223
} from 'react';
23-
// import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
2424
import { ReactScanInternals } from '..';
25+
import { useSharedValue, withTiming } from 'react-native-reanimated';
2526
import { assertNative, instrumentNative } from './instrument';
2627

27-
// can't use useSyncExternalStore for compat
28+
// can't use useSyncExternalStore for back compat
2829
const useIsPaused = () => {
2930
const [isPaused, setIsPaused] = useState(ReactScanInternals.isPaused);
3031
useEffect(() => {
@@ -36,18 +37,79 @@ const useIsPaused = () => {
3637
return isPaused;
3738
};
3839

39-
export const ReactNativeScanEntryPoint = () => {
40-
if (ReactScanInternals.isProd) {
41-
return null; // todo: better no-op
42-
}
40+
interface Options {
41+
/**
42+
* Controls the animation of the re-render overlay.
43+
* When set to "fade-out", the overlay will fade out after appearing.
44+
* When false, no animation will be applied.
45+
* Note: Enabling animations may impact performance.
46+
* @default false */
47+
animationWhenFlashing?: 'fade-out' | false;
48+
}
4349

50+
export type ReactNativeScanOptions =Options &
51+
Omit<
52+
typeof ReactScanInternals.options,
53+
'playSound' | 'runInProduction' | 'includeChildren'
54+
>;
55+
56+
const OptionsContext = createContext<ReactNativeScanOptions & Required<Options>>({
57+
animationWhenFlashing: false,
58+
});
59+
60+
export const ReactScan = ({
61+
children,
62+
...options
63+
}: {
64+
children: React.ReactNode;
65+
} & ReactNativeScanOptions) => {
4466
useEffect(() => {
45-
if (!ReactScanInternals.isProd) {
46-
instrumentNative(); // cleanup?
47-
}
67+
ReactScanInternals.options = options;
68+
instrumentNative();
4869
}, []);
4970
const isPaused = useIsPaused();
5071

72+
useEffect(() => {
73+
const interval = setInterval(() => {
74+
if (isPaused) return;
75+
76+
const newActive = ReactScanInternals.activeOutlines.filter(
77+
(x) => Date.now() - x.updatedAt < 500,
78+
);
79+
if (newActive.length !== ReactScanInternals.activeOutlines.length) {
80+
ReactScanInternals.set('activeOutlines', newActive);
81+
}
82+
}, 200);
83+
return () => {
84+
clearInterval(interval);
85+
};
86+
}, [isPaused]);
87+
88+
return (
89+
<>
90+
{children}
91+
<OptionsContext.Provider value={{
92+
...options,
93+
animationWhenFlashing: options.animationWhenFlashing ?? false
94+
}}>
95+
{!isPaused && <ReactScanCanvas scanTag="react-scan-no-traverse" />}
96+
{options.showToolbar && (
97+
<ReactScanToolbar
98+
scanTag="react-scan-no-traverse"
99+
isPaused={isPaused}
100+
/>
101+
)}
102+
</OptionsContext.Provider>
103+
</>
104+
);
105+
};
106+
107+
const ReactScanToolbar = ({
108+
isPaused,
109+
}: {
110+
isPaused: boolean;
111+
scanTag: string;
112+
}) => {
51113
const pan = useRef(new Animated.ValueXY()).current;
52114

53115
const panResponder = useRef(
@@ -70,77 +132,57 @@ export const ReactNativeScanEntryPoint = () => {
70132
}),
71133
).current;
72134

73-
useEffect(() => {
74-
const interval = setInterval(() => {
75-
if (isPaused) return;
76-
77-
const newActive = ReactScanInternals.activeOutlines.filter(
78-
(x) => Date.now() - x.updatedAt < 500,
79-
);
80-
if (newActive.length !== ReactScanInternals.activeOutlines.length) {
81-
ReactScanInternals.set('activeOutlines', newActive);
82-
}
83-
}, 200);
84-
return () => {
85-
clearInterval(interval);
86-
};
87-
}, [isPaused]);
88-
89135
return (
90-
<>
91-
{!isPaused && <ReactNativeScan id="react-scan-no-traverse" />}
92-
93-
<Animated.View
94-
id="react-scan-no-traverse"
136+
<Animated.View
137+
id="react-scan-no-traverse"
138+
style={{
139+
position: 'absolute',
140+
bottom: 20,
141+
right: 20,
142+
zIndex: 999999,
143+
transform: pan.getTranslateTransform(),
144+
}}
145+
{...panResponder.panHandlers}
146+
>
147+
<Pressable
148+
onPress={() =>
149+
(ReactScanInternals.isPaused = !ReactScanInternals.isPaused)
150+
}
95151
style={{
96-
position: 'absolute',
97-
bottom: 20,
98-
right: 20,
99-
zIndex: 999999,
100-
transform: pan.getTranslateTransform(),
152+
backgroundColor: !isPaused
153+
? 'rgba(88, 82, 185, 0.9)'
154+
: 'rgba(88, 82, 185, 0.5)',
155+
paddingHorizontal: 12,
156+
paddingVertical: 6,
157+
borderRadius: 4,
158+
flexDirection: 'row',
159+
alignItems: 'center',
160+
gap: 8,
101161
}}
102-
{...panResponder.panHandlers}
103162
>
104-
<Pressable
105-
onPress={() =>
106-
(ReactScanInternals.isPaused = !ReactScanInternals.isPaused)
107-
}
163+
<View
108164
style={{
109-
backgroundColor: !isPaused
110-
? 'rgba(88, 82, 185, 0.9)'
111-
: 'rgba(88, 82, 185, 0.5)',
112-
paddingHorizontal: 12,
113-
paddingVertical: 6,
165+
width: 8,
166+
height: 8,
114167
borderRadius: 4,
115-
flexDirection: 'row',
116-
alignItems: 'center',
117-
gap: 8,
168+
backgroundColor: !isPaused ? '#4ADE80' : '#666',
169+
}}
170+
/>
171+
<RNText
172+
style={{
173+
color: 'white',
174+
fontSize: 14,
175+
fontWeight: 'bold',
176+
fontFamily: Platform.select({
177+
ios: 'Courier',
178+
default: 'monospace',
179+
}),
118180
}}
119181
>
120-
<View
121-
style={{
122-
width: 8,
123-
height: 8,
124-
borderRadius: 4,
125-
backgroundColor: !isPaused ? '#4ADE80' : '#666',
126-
}}
127-
/>
128-
<RNText
129-
style={{
130-
color: 'white',
131-
fontSize: 14,
132-
fontWeight: 'bold',
133-
fontFamily: Platform.select({
134-
ios: 'Courier',
135-
default: 'monospace',
136-
}),
137-
}}
138-
>
139-
React Scan
140-
</RNText>
141-
</Pressable>
142-
</Animated.View>
143-
</>
182+
React Scan
183+
</RNText>
184+
</Pressable>
185+
</Animated.View>
144186
);
145187
};
146188
const dimensions = Dimensions.get('window');
@@ -155,24 +197,30 @@ const font = matchFont({
155197
const getTextWidth = (text: string) => {
156198
return (text || 'unknown').length * 7;
157199
};
158-
const ReactNativeScan = ({ id: _ }: { id: string }) => {
159-
// const opacity = useSharedValue(1);
160-
// todo: polly fill
161-
const outlines = useSyncExternalStore(
162-
(listener) =>
163-
ReactScanInternals.subscribe('activeOutlines', (_) => {
164-
// animations destroy UI thread on heavy updates, probably not worth it
165-
// opacity.value = 1;
166-
// opacity.value = withTiming(0, {
167-
// duration: 500
168-
// })
169-
listener();
170-
}),
171-
() => ReactScanInternals.activeOutlines,
172-
);
173-
// );
174-
// const animatedOpacity = useDerivedValue(() => opacity.value);
175200

201+
const useOutlines = (opacity: { value: number }) => {
202+
const [outlines, setOutlines] = useState<
203+
(typeof ReactScanInternals)['activeOutlines']
204+
>([]);
205+
const options = useContext(OptionsContext);
206+
// cannot use useSyncExternalStore for back compat
207+
useEffect(() => {
208+
ReactScanInternals.subscribe('activeOutlines', (activeOutlines) => {
209+
setOutlines(activeOutlines);
210+
if (options.animationWhenFlashing !== false) {
211+
// we only support fade-out for now
212+
opacity.value = 1;
213+
opacity.value = withTiming(0, {
214+
duration: 500,
215+
});
216+
}
217+
});
218+
}, []);
219+
return outlines;
220+
};
221+
const ReactScanCanvas = (_: { scanTag: string }) => {
222+
const opacity = useSharedValue(1);
223+
const outlines = useOutlines(opacity);
176224
return (
177225
<Canvas
178226
style={{
@@ -255,8 +303,3 @@ const ReactNativeScan = ({ id: _ }: { id: string }) => {
255303
</Canvas>
256304
);
257305
};
258-
259-
260-
261-
262-

0 commit comments

Comments
 (0)