Skip to content

Commit ed6accb

Browse files
committed
potential resolve react-compiler compatiiblity
1 parent 38414f4 commit ed6accb

2 files changed

Lines changed: 86 additions & 146 deletions

File tree

src/components/Charts/hooks/useChartInteractionState.ts

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {useMemo, useState} from 'react';
2-
import type {SharedValue} from 'react-native-reanimated';
3-
import {makeMutable, useAnimatedReaction} from 'react-native-reanimated';
4-
import {scheduleOnRN} from 'react-native-worklets';
1+
import { useState } from 'react';
2+
import type { SharedValue } from 'react-native-reanimated';
3+
import { makeMutable, useAnimatedReaction } from 'react-native-reanimated';
4+
import { scheduleOnRN } from 'react-native-worklets';
55

66
/**
77
* Input field type - matches Victory Native's InputFieldType
@@ -71,62 +71,41 @@ function useIsInteractionActive<Init extends ChartInteractionStateInit>(state: C
7171
*
7272
* @param initialValues - Initial x and y values matching your chart data structure
7373
* @returns Object containing the interaction state and a boolean indicating if interaction is active
74-
*
75-
* @example
76-
* ```tsx
77-
* const { state, isActive } = useChartInteractionState({
78-
* x: '',
79-
* y: { value: 0 }
80-
* });
81-
*
82-
* // Use with customGestures and actionsRef
83-
* const hoverGesture = Gesture.Hover()
84-
* .onUpdate((e) => {
85-
* state.isActive.set(true);
86-
* actionsRef.current?.handleTouch(state, e.x, e.y);
87-
* })
88-
* .onEnd(() => {
89-
* state.isActive.set(false);
90-
* });
91-
* ```
9274
*/
9375
function useChartInteractionState<Init extends ChartInteractionStateInit>(initialValues: Init): {
9476
state: ChartInteractionState<Init>;
9577
isActive: boolean;
9678
} {
97-
const keys = Object.keys(initialValues.y).join(',');
98-
99-
const state = useMemo(() => {
100-
const yState = {} as Record<keyof Init['y'], {value: SharedValue<number>; position: SharedValue<number>}>;
101-
102-
for (const [key, initVal] of Object.entries(initialValues.y)) {
103-
yState[key as keyof Init['y']] = {
104-
value: makeMutable(initVal),
105-
position: makeMutable(0),
106-
};
107-
}
79+
// The React Compiler will automatically memoize this object creation.
80+
// We remove the explicit useMemo and dependency on 'keys'.
81+
const yState = {} as Record<keyof Init['y'], { value: SharedValue<number>; position: SharedValue<number> }>;
10882

109-
return {
110-
isActive: makeMutable(false),
111-
matchedIndex: makeMutable(-1),
112-
x: {
113-
value: makeMutable(initialValues.x),
114-
position: makeMutable(0),
115-
},
116-
y: yState,
117-
yIndex: makeMutable(-1),
118-
cursor: {
119-
x: makeMutable(0),
120-
y: makeMutable(0),
121-
},
83+
for (const [key, initVal] of Object.entries(initialValues.y)) {
84+
yState[key as keyof Init['y']] = {
85+
value: makeMutable(initVal),
86+
position: makeMutable(0),
12287
};
123-
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- keys is a stable string representation of y keys
124-
}, [keys]);
88+
}
89+
90+
const state: ChartInteractionState<Init> = {
91+
isActive: makeMutable(false),
92+
matchedIndex: makeMutable(-1),
93+
x: {
94+
value: makeMutable(initialValues.x),
95+
position: makeMutable(0),
96+
},
97+
y: yState,
98+
yIndex: makeMutable(-1),
99+
cursor: {
100+
x: makeMutable(0),
101+
y: makeMutable(0),
102+
},
103+
};
125104

126105
const isActive = useIsInteractionActive(state);
127106

128-
return {state, isActive};
107+
return { state, isActive };
129108
}
130109

131-
export {useChartInteractionState};
132-
export type {ChartInteractionState, ChartInteractionStateInit};
110+
export { useChartInteractionState };
111+
export type { ChartInteractionState, ChartInteractionStateInit };
Lines changed: 56 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import {useMemo, useRef, useState} from 'react';
2-
import {Gesture} from 'react-native-gesture-handler';
3-
import type {SharedValue} from 'react-native-reanimated';
4-
import {useAnimatedReaction, useAnimatedStyle, useDerivedValue} from 'react-native-reanimated';
5-
import {scheduleOnRN} from 'react-native-worklets';
6-
import {TOOLTIP_BAR_GAP} from '@components/Charts/constants';
7-
import {useChartInteractionState} from './useChartInteractionState';
1+
import { useRef, useState } from 'react';
2+
import { Gesture } from 'react-native-gesture-handler';
3+
import type { SharedValue } from 'react-native-reanimated';
4+
import { useAnimatedReaction, useAnimatedStyle, useDerivedValue } from 'react-native-reanimated';
5+
import { scheduleOnRN } from 'react-native-worklets';
6+
import { TOOLTIP_BAR_GAP } from '@components/Charts/constants';
7+
import { useChartInteractionState } from './useChartInteractionState';
88

99
/**
1010
* Arguments passed to the checkIsOver callback for hit-testing
@@ -34,7 +34,7 @@ type UseChartInteractionsProps = {
3434
*/
3535
checkIsOver: (args: HitTestArgs) => boolean;
3636
/** Optional shared value containing bar dimensions used for hit-testing in bar charts */
37-
barGeometry?: SharedValue<{barWidth: number; chartBottom: number; yZero: number}>;
37+
barGeometry?: SharedValue<{ barWidth: number; chartBottom: number; yZero: number }>;
3838
};
3939

4040
/**
@@ -74,29 +74,18 @@ type CartesianActionsHandle = {
7474
* );
7575
* ```
7676
*/
77-
function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartInteractionsProps) {
78-
/** Interaction state compatible with Victory Native's internal logic */
79-
const {state: chartInteractionState, isActive: isTooltipActiveState} = useChartInteractionState({x: 0, y: {y: 0}});
80-
81-
/** Ref passed to CartesianChart to allow manual touch injection */
77+
function useChartInteractions({ handlePress, checkIsOver, barGeometry }: UseChartInteractionsProps) {
78+
const { state: chartInteractionState, isActive: isTooltipActiveState } = useChartInteractionState({ x: 0, y: { y: 0 } });
8279
const actionsRef = useRef<CartesianActionsHandle>(null);
8380

84-
/** React state for the index of the point currently being interacted with */
8581
const [activeDataIndex, setActiveDataIndex] = useState(-1);
86-
87-
/** React state indicating if the cursor is currently "hitting" a target based on checkIsOver */
8882
const [isOverTarget, setIsOverTarget] = useState(false);
8983

90-
/**
91-
* Derived value performing the hit-test on the UI thread.
92-
* Runs whenever cursor position or matched data points change.
93-
*/
9484
const isCursorOverTarget = useDerivedValue(() => {
9585
const cursorX = chartInteractionState.cursor.x.get();
9686
const cursorY = chartInteractionState.cursor.y.get();
9787
const targetX = chartInteractionState.x.position.get();
9888
const targetY = chartInteractionState.y.y.position.get();
99-
10089
const chartBottom = barGeometry?.get().chartBottom ?? 0;
10190

10291
return checkIsOver({
@@ -108,113 +97,85 @@ function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartI
10897
});
10998
});
11099

111-
/** Syncs the matched data index from the UI thread to React state */
112100
useAnimatedReaction(
113101
() => chartInteractionState.matchedIndex.get(),
114102
(currentIndex) => {
115103
scheduleOnRN(setActiveDataIndex, currentIndex);
116104
},
117105
);
118106

119-
/** Syncs the hit-test result from the UI thread to React state */
120107
useAnimatedReaction(
121108
() => isCursorOverTarget.get(),
122109
(isOver) => {
123110
scheduleOnRN(setIsOverTarget, isOver);
124111
},
125112
);
126113

127-
/**
128-
* Hover gesture configuration.
129-
* Primarily used for web/desktop to track mouse movement without clicking.
130-
*/
131-
const hoverGesture = useMemo(
132-
() =>
133-
Gesture.Hover()
134-
.onBegin((e) => {
135-
'worklet';
136-
137-
chartInteractionState.isActive.set(true);
138-
chartInteractionState.cursor.x.set(e.x);
139-
chartInteractionState.cursor.y.set(e.y);
140-
actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y);
141-
})
142-
.onUpdate((e) => {
143-
'worklet';
144-
145-
chartInteractionState.cursor.x.set(e.x);
146-
chartInteractionState.cursor.y.set(e.y);
147-
actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y);
148-
})
149-
.onEnd(() => {
150-
'worklet';
151-
152-
chartInteractionState.isActive.set(false);
153-
}),
154-
[chartInteractionState],
155-
);
114+
// React Compiler automatycznie zmemoizuje te obiekty gestów
115+
const hoverGesture = Gesture.Hover()
116+
.onBegin((e) => {
117+
'worklet';
118+
119+
chartInteractionState.isActive.set(true);
120+
chartInteractionState.cursor.x.set(e.x);
121+
chartInteractionState.cursor.y.set(e.y);
122+
actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y);
123+
})
124+
.onUpdate((e) => {
125+
'worklet';
126+
127+
chartInteractionState.cursor.x.set(e.x);
128+
chartInteractionState.cursor.y.set(e.y);
129+
actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y);
130+
})
131+
.onEnd(() => {
132+
'worklet';
133+
134+
chartInteractionState.isActive.set(false);
135+
});
156136

157-
/**
158-
* Tap gesture configuration.
159-
* Handles clicks/touches and triggers handlePress if Victory matched a data point.
160-
*/
161-
const tapGesture = useMemo(
162-
() =>
163-
Gesture.Tap().onEnd((e) => {
164-
'worklet';
165-
166-
// Update cursor position
167-
chartInteractionState.cursor.x.set(e.x);
168-
chartInteractionState.cursor.y.set(e.y);
169-
170-
// Let Victory calculate which data point was tapped
171-
actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y);
172-
const matchedIndex = chartInteractionState.matchedIndex.get();
173-
174-
// If Victory matched a valid data point, trigger the press handler
175-
if (matchedIndex >= 0) {
176-
scheduleOnRN(handlePress, matchedIndex);
177-
}
178-
}),
179-
[chartInteractionState, handlePress],
180-
);
137+
const tapGesture = Gesture.Tap().onEnd((e) => {
138+
'worklet';
181139

182-
/** Combined gesture object to be passed to CartesianChart's customGestures prop */
183-
const customGestures = useMemo(() => Gesture.Race(hoverGesture, tapGesture), [hoverGesture, tapGesture]);
140+
chartInteractionState.cursor.x.set(e.x);
141+
chartInteractionState.cursor.y.set(e.y);
142+
143+
actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y);
144+
const matchedIndex = chartInteractionState.matchedIndex.get();
145+
const isOver = isCursorOverTarget.get();
146+
147+
if (matchedIndex >= 0 && isOver) {
148+
scheduleOnRN(handlePress, matchedIndex);
149+
}
150+
});
151+
152+
const customGestures = Gesture.Race(hoverGesture, tapGesture);
184153

185-
/**
186-
* Animated style for positioning a tooltip relative to the matched data point.
187-
* Automatically applies vertical offset and centering.
188-
* For negative bars, positions tooltip at yZero (top of bar) instead of targetY (bottom of bar).
189-
*/
190154
const tooltipStyle = useAnimatedStyle(() => {
155+
const posX = chartInteractionState.x.position.get();
191156
const targetY = chartInteractionState.y.y.position.get();
192157
const yZero = barGeometry?.get().yZero ?? targetY;
193-
// Position tooltip at the top of the bar (min of targetY and yZero)
194158
const barTopY = Math.min(targetY, yZero);
195159

160+
const isVisible = chartInteractionState.isActive.get() && isCursorOverTarget.get();
161+
196162
return {
197163
position: 'absolute',
198-
left: chartInteractionState.x.position.get(),
164+
left: posX,
199165
top: barTopY - TOOLTIP_BAR_GAP,
200-
transform: [{translateX: '-50%'}, {translateY: '-100%'}],
201-
opacity: chartInteractionState.isActive.get() ? 1 : 0,
166+
transform: [{ translateX: '-50%' }, { translateY: '-100%' }],
167+
opacity: isVisible ? 1 : 0,
202168
};
203169
});
204170

205171
return {
206-
/** Ref to be passed to CartesianChart */
207172
actionsRef,
208-
/** Gestures to be passed to CartesianChart */
209173
customGestures,
210-
/** The currently active data index (React state) */
211174
activeDataIndex,
212-
/** Whether the tooltip should currently be rendered and visible */
213-
isTooltipActive: isOverTarget && isTooltipActiveState,
214-
/** Animated styles for the tooltip container */
175+
isTooltipActive: isTooltipActiveState && isOverTarget,
215176
tooltipStyle,
216177
};
217178
}
218179

219-
export {useChartInteractions};
220-
export type {HitTestArgs};
180+
export { useChartInteractions };
181+
export type { HitTestArgs };

0 commit comments

Comments
 (0)