Skip to content

Commit debbdb5

Browse files
committed
Refactor event handler extraction in shape components
Replaces explicit event handler destructuring and useMemo logic in all shape components with a generic extraction using EVENT_HANDLER_NAMES. This improves maintainability and reduces repetitive code by automatically separating event handlers from shape props.
1 parent e7d857d commit debbdb5

File tree

10 files changed

+210
-540
lines changed

10 files changed

+210
-540
lines changed

lib/ArcSegment.tsx

Lines changed: 21 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useTwo } from './Context';
55
import type { ArcSegment as Instance } from 'two.js/src/shapes/arc-segment';
66
import { PathProps } from './Path';
77
import { type EventHandlers } from './Properties';
8+
import { EVENT_HANDLER_NAMES } from './Events';
89

910
type ArcSegmentProps =
1011
| PathProps
@@ -25,29 +26,7 @@ type ComponentProps = React.PropsWithChildren<
2526
export type RefArcSegment = Instance;
2627

2728
export const ArcSegment = React.forwardRef<Instance, ComponentProps>(
28-
(
29-
{
30-
x,
31-
y,
32-
resolution,
33-
// Event handlers
34-
onClick,
35-
onContextMenu,
36-
onDoubleClick,
37-
onWheel,
38-
onPointerDown,
39-
onPointerUp,
40-
onPointerOver,
41-
onPointerOut,
42-
onPointerEnter,
43-
onPointerLeave,
44-
onPointerMove,
45-
onPointerCancel,
46-
// All other props are shape props
47-
...shapeProps
48-
},
49-
forwardedRef
50-
) => {
29+
({ x, y, resolution, ...props }, forwardedRef) => {
5130
const { parent, registerEventShape, unregisterEventShape } = useTwo();
5231

5332
// Create the instance synchronously so it's available for refs immediately
@@ -56,37 +35,25 @@ export const ArcSegment = React.forwardRef<Instance, ComponentProps>(
5635
[resolution]
5736
);
5837

59-
// Build event handlers object with explicit dependencies
60-
const eventHandlers = useMemo(
61-
() => ({
62-
...(onClick && { onClick }),
63-
...(onContextMenu && { onContextMenu }),
64-
...(onDoubleClick && { onDoubleClick }),
65-
...(onWheel && { onWheel }),
66-
...(onPointerDown && { onPointerDown }),
67-
...(onPointerUp && { onPointerUp }),
68-
...(onPointerOver && { onPointerOver }),
69-
...(onPointerOut && { onPointerOut }),
70-
...(onPointerEnter && { onPointerEnter }),
71-
...(onPointerLeave && { onPointerLeave }),
72-
...(onPointerMove && { onPointerMove }),
73-
...(onPointerCancel && { onPointerCancel }),
74-
}),
75-
[
76-
onClick,
77-
onContextMenu,
78-
onDoubleClick,
79-
onWheel,
80-
onPointerDown,
81-
onPointerUp,
82-
onPointerOver,
83-
onPointerOut,
84-
onPointerEnter,
85-
onPointerLeave,
86-
onPointerMove,
87-
onPointerCancel,
88-
]
89-
);
38+
// Extract event handlers from props
39+
const { eventHandlers, shapeProps } = useMemo(() => {
40+
const eventHandlers: Partial<EventHandlers> = {};
41+
const shapeProps: Record<string, unknown> = {};
42+
43+
for (const key in props) {
44+
if (EVENT_HANDLER_NAMES.includes(key as keyof EventHandlers)) {
45+
eventHandlers[key as keyof EventHandlers] = props[
46+
key as keyof EventHandlers
47+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48+
] as any;
49+
} else {
50+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
51+
shapeProps[key] = (props as any)[key];
52+
}
53+
}
54+
55+
return { eventHandlers, shapeProps };
56+
}, [props]);
9057

9158
useEffect(() => {
9259
if (parent) {

lib/Circle.tsx

Lines changed: 21 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useTwo } from './Context';
55
import type { Circle as Instance } from 'two.js/src/shapes/circle';
66
import { PathProps } from './Path';
77
import { type EventHandlers } from './Properties';
8+
import { EVENT_HANDLER_NAMES } from './Events';
89

910
type CircleProps = PathProps | 'radius';
1011
type ComponentProps = React.PropsWithChildren<
@@ -20,29 +21,7 @@ type ComponentProps = React.PropsWithChildren<
2021
export type RefCircle = Instance;
2122

2223
export const Circle = React.forwardRef<Instance, ComponentProps>(
23-
(
24-
{
25-
x,
26-
y,
27-
resolution,
28-
// Event handlers
29-
onClick,
30-
onContextMenu,
31-
onDoubleClick,
32-
onWheel,
33-
onPointerDown,
34-
onPointerUp,
35-
onPointerOver,
36-
onPointerOut,
37-
onPointerEnter,
38-
onPointerLeave,
39-
onPointerMove,
40-
onPointerCancel,
41-
// All other props are shape props
42-
...shapeProps
43-
},
44-
forwardedRef
45-
) => {
24+
({ x, y, resolution, ...props }, forwardedRef) => {
4625
const { parent, registerEventShape, unregisterEventShape } = useTwo();
4726

4827
// Create the instance synchronously so it's available for refs immediately
@@ -51,37 +30,25 @@ export const Circle = React.forwardRef<Instance, ComponentProps>(
5130
[resolution]
5231
);
5332

54-
// Build event handlers object with explicit dependencies
55-
const eventHandlers = useMemo(
56-
() => ({
57-
...(onClick && { onClick }),
58-
...(onContextMenu && { onContextMenu }),
59-
...(onDoubleClick && { onDoubleClick }),
60-
...(onWheel && { onWheel }),
61-
...(onPointerDown && { onPointerDown }),
62-
...(onPointerUp && { onPointerUp }),
63-
...(onPointerOver && { onPointerOver }),
64-
...(onPointerOut && { onPointerOut }),
65-
...(onPointerEnter && { onPointerEnter }),
66-
...(onPointerLeave && { onPointerLeave }),
67-
...(onPointerMove && { onPointerMove }),
68-
...(onPointerCancel && { onPointerCancel }),
69-
}),
70-
[
71-
onClick,
72-
onContextMenu,
73-
onDoubleClick,
74-
onWheel,
75-
onPointerDown,
76-
onPointerUp,
77-
onPointerOver,
78-
onPointerOut,
79-
onPointerEnter,
80-
onPointerLeave,
81-
onPointerMove,
82-
onPointerCancel,
83-
]
84-
);
33+
// Extract event handlers from props
34+
const { eventHandlers, shapeProps } = useMemo(() => {
35+
const eventHandlers: Partial<EventHandlers> = {};
36+
const shapeProps: Record<string, unknown> = {};
37+
38+
for (const key in props) {
39+
if (EVENT_HANDLER_NAMES.includes(key as keyof EventHandlers)) {
40+
eventHandlers[key as keyof EventHandlers] = props[
41+
key as keyof EventHandlers
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43+
] as any;
44+
} else {
45+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
46+
shapeProps[key] = (props as any)[key];
47+
}
48+
}
49+
50+
return { eventHandlers, shapeProps };
51+
}, [props]);
8552

8653
useEffect(() => {
8754
// Update position

lib/Image.tsx

Lines changed: 21 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Image as Instance } from 'two.js/src/effects/image';
66
import { RectangleProps } from './Rectangle';
77
import type { Texture } from 'two.js/src/effects/texture';
88
import { type EventHandlers } from './Properties';
9+
import { EVENT_HANDLER_NAMES } from './Events';
910

1011
type ImageProps = RectangleProps | 'mode' | 'texture';
1112

@@ -24,67 +25,31 @@ type ComponentProps = React.PropsWithChildren<
2425
export type RefImage = Instance;
2526

2627
export const Image = React.forwardRef<Instance, ComponentProps>(
27-
(
28-
{
29-
mode,
30-
src,
31-
texture,
32-
x,
33-
y,
34-
// Event handlers
35-
onClick,
36-
onContextMenu,
37-
onDoubleClick,
38-
onWheel,
39-
onPointerDown,
40-
onPointerUp,
41-
onPointerOver,
42-
onPointerOut,
43-
onPointerEnter,
44-
onPointerLeave,
45-
onPointerMove,
46-
onPointerCancel,
47-
// All other props are shape props
48-
...shapeProps
49-
},
50-
forwardedRef
51-
) => {
28+
({ mode, src, texture, x, y, ...props }, forwardedRef) => {
5229
const { parent, registerEventShape, unregisterEventShape } = useTwo();
5330

5431
// Create the instance synchronously so it's available for refs immediately
5532
const image = useMemo(() => new Two.Image(src), [src]);
5633

57-
// Build event handlers object with explicit dependencies
58-
const eventHandlers = useMemo(
59-
() => ({
60-
...(onClick && { onClick }),
61-
...(onContextMenu && { onContextMenu }),
62-
...(onDoubleClick && { onDoubleClick }),
63-
...(onWheel && { onWheel }),
64-
...(onPointerDown && { onPointerDown }),
65-
...(onPointerUp && { onPointerUp }),
66-
...(onPointerOver && { onPointerOver }),
67-
...(onPointerOut && { onPointerOut }),
68-
...(onPointerEnter && { onPointerEnter }),
69-
...(onPointerLeave && { onPointerLeave }),
70-
...(onPointerMove && { onPointerMove }),
71-
...(onPointerCancel && { onPointerCancel }),
72-
}),
73-
[
74-
onClick,
75-
onContextMenu,
76-
onDoubleClick,
77-
onWheel,
78-
onPointerDown,
79-
onPointerUp,
80-
onPointerOver,
81-
onPointerOut,
82-
onPointerEnter,
83-
onPointerLeave,
84-
onPointerMove,
85-
onPointerCancel,
86-
]
87-
);
34+
// Extract event handlers from props
35+
const { eventHandlers, shapeProps } = useMemo(() => {
36+
const eventHandlers: Partial<EventHandlers> = {};
37+
const shapeProps: Record<string, unknown> = {};
38+
39+
for (const key in props) {
40+
if (EVENT_HANDLER_NAMES.includes(key as keyof EventHandlers)) {
41+
eventHandlers[key as keyof EventHandlers] = props[
42+
key as keyof EventHandlers
43+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
44+
] as any;
45+
} else {
46+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
47+
shapeProps[key] = (props as any)[key];
48+
}
49+
}
50+
51+
return { eventHandlers, shapeProps };
52+
}, [props]);
8853

8954
useEffect(() => {
9055
if (parent) {

lib/Line.tsx

Lines changed: 21 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useTwo } from './Context';
55
import type { Line as Instance } from 'two.js/src/shapes/line';
66
import { PathProps } from './Path';
77
import { type EventHandlers } from './Properties';
8+
import { EVENT_HANDLER_NAMES } from './Events';
89

910
type LineProps = PathProps | 'left' | 'right';
1011
type ComponentProps = React.PropsWithChildren<
@@ -21,66 +22,31 @@ type ComponentProps = React.PropsWithChildren<
2122
export type RefLine = Instance;
2223

2324
export const Line = React.forwardRef<Instance, ComponentProps>(
24-
(
25-
{
26-
x1,
27-
y1,
28-
x2,
29-
y2,
30-
// Event handlers
31-
onClick,
32-
onContextMenu,
33-
onDoubleClick,
34-
onWheel,
35-
onPointerDown,
36-
onPointerUp,
37-
onPointerOver,
38-
onPointerOut,
39-
onPointerEnter,
40-
onPointerLeave,
41-
onPointerMove,
42-
onPointerCancel,
43-
// All other props are shape props
44-
...shapeProps
45-
},
46-
forwardedRef
47-
) => {
25+
({ x1, y1, x2, y2, ...props }, forwardedRef) => {
4826
const { parent, registerEventShape, unregisterEventShape } = useTwo();
4927

5028
// Create the instance synchronously so it's available for refs immediately
5129
const line = useMemo(() => new Two.Line(), []);
5230

53-
// Build event handlers object with explicit dependencies
54-
const eventHandlers = useMemo(
55-
() => ({
56-
...(onClick && { onClick }),
57-
...(onContextMenu && { onContextMenu }),
58-
...(onDoubleClick && { onDoubleClick }),
59-
...(onWheel && { onWheel }),
60-
...(onPointerDown && { onPointerDown }),
61-
...(onPointerUp && { onPointerUp }),
62-
...(onPointerOver && { onPointerOver }),
63-
...(onPointerOut && { onPointerOut }),
64-
...(onPointerEnter && { onPointerEnter }),
65-
...(onPointerLeave && { onPointerLeave }),
66-
...(onPointerMove && { onPointerMove }),
67-
...(onPointerCancel && { onPointerCancel }),
68-
}),
69-
[
70-
onClick,
71-
onContextMenu,
72-
onDoubleClick,
73-
onWheel,
74-
onPointerDown,
75-
onPointerUp,
76-
onPointerOver,
77-
onPointerOut,
78-
onPointerEnter,
79-
onPointerLeave,
80-
onPointerMove,
81-
onPointerCancel,
82-
]
83-
);
31+
// Extract event handlers from props
32+
const { eventHandlers, shapeProps } = useMemo(() => {
33+
const eventHandlers: Partial<EventHandlers> = {};
34+
const shapeProps: Record<string, unknown> = {};
35+
36+
for (const key in props) {
37+
if (EVENT_HANDLER_NAMES.includes(key as keyof EventHandlers)) {
38+
eventHandlers[key as keyof EventHandlers] = props[
39+
key as keyof EventHandlers
40+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
41+
] as any;
42+
} else {
43+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
44+
shapeProps[key] = (props as any)[key];
45+
}
46+
}
47+
48+
return { eventHandlers, shapeProps };
49+
}, [props]);
8450

8551
useEffect(() => {
8652
if (parent) {

0 commit comments

Comments
 (0)