@reactuses/core 6.1.10 → 6.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -113,10 +113,57 @@ const useDeepCompareEffect = (effect, deps)=>{
113
113
  useCustomCompareEffect(effect, deps, lodashEs.isEqual);
114
114
  };
115
115
 
116
+ /**
117
+ * Creates a stable identifier for a BasicTarget that can be safely used in effect dependencies.
118
+ *
119
+ * This hook solves the problem where passing unstable function references like `() => document`
120
+ * would cause infinite re-renders when used directly in effect dependency arrays.
121
+ *
122
+ * @param target - The target element (ref, function, or direct element)
123
+ * @param defaultElement - Default element to use if target is undefined
124
+ * @returns A stable reference that only changes when the actual target element changes
125
+ *
126
+ * @example
127
+ * ```tsx
128
+ * // For ref objects: returns the ref itself (stable)
129
+ * const ref = useRef<HTMLDivElement>(null)
130
+ * const key = useStableTarget(ref) // key === ref (stable)
131
+ *
132
+ * // For functions: returns the resolved actual element
133
+ * const key = useStableTarget(() => document) // key === document (stable)
134
+ *
135
+ * // For direct elements: returns the element itself
136
+ * const key = useStableTarget(divElement) // key === divElement (stable)
137
+ * ```
138
+ */ function useStableTarget(target, defaultElement) {
139
+ const targetRef = React.useRef(target);
140
+ targetRef.current = target;
141
+ // Calculate stable key without memoization
142
+ // For ref objects: return the ref itself (always stable)
143
+ // For functions/direct elements: resolve to the actual element
144
+ let stableKey;
145
+ if (!target) {
146
+ stableKey = defaultElement != null ? defaultElement : null;
147
+ } else if (typeof target === 'object' && 'current' in target) {
148
+ // Ref object - use the ref itself as the stable key
149
+ stableKey = target;
150
+ } else {
151
+ // Function or direct element - resolve to actual element
152
+ stableKey = getTargetElement(target, defaultElement);
153
+ }
154
+ return {
155
+ /** The stable key that can be safely used in effect dependencies */ key: stableKey,
156
+ /** A ref containing the current target (useful for accessing in effects) */ ref: targetRef
157
+ };
158
+ }
159
+
116
160
  function useEventListenerImpl(eventName, handler, element, options = defaultOptions$1) {
117
161
  const savedHandler = useLatest(handler);
118
- const targetElement = getTargetElement(element, defaultWindow);
162
+ const { key: elementKey, ref: elementRef } = useStableTarget(element, defaultWindow);
119
163
  useDeepCompareEffect(()=>{
164
+ // Call getTargetElement inside effect to support ref-based targets
165
+ // (ref.current is null during render, only available in commit phase)
166
+ const targetElement = getTargetElement(elementRef.current, defaultWindow);
120
167
  if (!(targetElement && targetElement.addEventListener)) {
121
168
  return;
122
169
  }
@@ -130,7 +177,7 @@ function useEventListenerImpl(eventName, handler, element, options = defaultOpti
130
177
  };
131
178
  }, [
132
179
  eventName,
133
- targetElement,
180
+ elementKey,
134
181
  options
135
182
  ]);
136
183
  }
@@ -1099,13 +1146,14 @@ const useDropZone = (target, onDrop)=>{
1099
1146
  const useResizeObserver = (target, callback, options = defaultOptions$1)=>{
1100
1147
  const savedCallback = useLatest(callback);
1101
1148
  const observerRef = React.useRef();
1149
+ const { key: targetKey, ref: targetRef } = useStableTarget(target);
1102
1150
  const stop = React.useCallback(()=>{
1103
1151
  if (observerRef.current) {
1104
1152
  observerRef.current.disconnect();
1105
1153
  }
1106
1154
  }, []);
1107
1155
  useDeepCompareEffect(()=>{
1108
- const element = getTargetElement(target);
1156
+ const element = getTargetElement(targetRef.current);
1109
1157
  if (!element) {
1110
1158
  return;
1111
1159
  }
@@ -1113,9 +1161,7 @@ const useResizeObserver = (target, callback, options = defaultOptions$1)=>{
1113
1161
  observerRef.current.observe(element, options);
1114
1162
  return stop;
1115
1163
  }, [
1116
- savedCallback,
1117
- stop,
1118
- target,
1164
+ targetKey,
1119
1165
  options
1120
1166
  ]);
1121
1167
  return stop;
@@ -1226,13 +1272,14 @@ const useElementSize = (target, options = defaultOptions$1)=>{
1226
1272
  const useIntersectionObserver = (target, callback, options = defaultOptions$1)=>{
1227
1273
  const savedCallback = useLatest(callback);
1228
1274
  const observerRef = React.useRef();
1275
+ const { key: targetKey, ref: targetRef } = useStableTarget(target);
1229
1276
  const stop = React.useCallback(()=>{
1230
1277
  if (observerRef.current) {
1231
1278
  observerRef.current.disconnect();
1232
1279
  }
1233
1280
  }, []);
1234
1281
  useDeepCompareEffect(()=>{
1235
- const element = getTargetElement(target);
1282
+ const element = getTargetElement(targetRef.current);
1236
1283
  if (!element) {
1237
1284
  return;
1238
1285
  }
@@ -1240,6 +1287,7 @@ const useIntersectionObserver = (target, callback, options = defaultOptions$1)=>
1240
1287
  observerRef.current.observe(element);
1241
1288
  return stop;
1242
1289
  }, [
1290
+ targetKey,
1243
1291
  options
1244
1292
  ]);
1245
1293
  return stop;
@@ -2345,13 +2393,14 @@ const useMousePressed = (target, options = defaultOptions$1)=>{
2345
2393
  const useMutationObserver = (callback, target, options = defaultOptions$1)=>{
2346
2394
  const callbackRef = useLatest(callback);
2347
2395
  const observerRef = React.useRef();
2396
+ const { key: targetKey, ref: targetRef } = useStableTarget(target);
2348
2397
  const stop = React.useCallback(()=>{
2349
2398
  if (observerRef.current) {
2350
2399
  observerRef.current.disconnect();
2351
2400
  }
2352
2401
  }, []);
2353
2402
  useDeepCompareEffect(()=>{
2354
- const element = getTargetElement(target);
2403
+ const element = getTargetElement(targetRef.current);
2355
2404
  if (!element) {
2356
2405
  return;
2357
2406
  }
@@ -2359,6 +2408,7 @@ const useMutationObserver = (callback, target, options = defaultOptions$1)=>{
2359
2408
  observerRef.current.observe(element, options);
2360
2409
  return stop;
2361
2410
  }, [
2411
+ targetKey,
2362
2412
  options
2363
2413
  ]);
2364
2414
  return stop;
@@ -3173,21 +3223,25 @@ const useSetState = (initialState)=>{
3173
3223
 
3174
3224
  function useSticky(targetElement, { axis = 'y', nav = 0 }, scrollElement) {
3175
3225
  const [isSticky, setSticky] = React.useState(false);
3226
+ const { key: targetKey, ref: targetRef } = useStableTarget(targetElement);
3227
+ const { key: scrollKey, ref: scrollRef } = useStableTarget(scrollElement);
3228
+ const axisRef = useLatest(axis);
3229
+ const navRef = useLatest(nav);
3176
3230
  const { run: scrollHandler } = useThrottleFn(()=>{
3177
- const element = getTargetElement(targetElement);
3231
+ const element = getTargetElement(targetRef.current);
3178
3232
  if (!element) {
3179
3233
  return;
3180
3234
  }
3181
3235
  const rect = element.getBoundingClientRect();
3182
- if (axis === 'y') {
3183
- setSticky((rect == null ? void 0 : rect.top) <= nav);
3236
+ if (axisRef.current === 'y') {
3237
+ setSticky((rect == null ? void 0 : rect.top) <= navRef.current);
3184
3238
  } else {
3185
- setSticky((rect == null ? void 0 : rect.left) <= nav);
3239
+ setSticky((rect == null ? void 0 : rect.left) <= navRef.current);
3186
3240
  }
3187
3241
  }, 50);
3188
3242
  React.useEffect(()=>{
3189
- const element = getTargetElement(targetElement);
3190
- const scrollParent = getTargetElement(scrollElement) || getScrollParent(axis, element);
3243
+ const element = getTargetElement(targetRef.current);
3244
+ const scrollParent = getTargetElement(scrollRef.current) || getScrollParent(axisRef.current, element);
3191
3245
  if (!element || !scrollParent) {
3192
3246
  return;
3193
3247
  }
@@ -3197,9 +3251,8 @@ function useSticky(targetElement, { axis = 'y', nav = 0 }, scrollElement) {
3197
3251
  scrollParent.removeEventListener('scroll', scrollHandler);
3198
3252
  };
3199
3253
  }, [
3200
- axis,
3201
- targetElement,
3202
- scrollElement,
3254
+ targetKey,
3255
+ scrollKey,
3203
3256
  scrollHandler
3204
3257
  ]);
3205
3258
  return [
package/dist/index.mjs CHANGED
@@ -105,10 +105,57 @@ const useDeepCompareEffect = (effect, deps)=>{
105
105
  useCustomCompareEffect(effect, deps, isEqual);
106
106
  };
107
107
 
108
+ /**
109
+ * Creates a stable identifier for a BasicTarget that can be safely used in effect dependencies.
110
+ *
111
+ * This hook solves the problem where passing unstable function references like `() => document`
112
+ * would cause infinite re-renders when used directly in effect dependency arrays.
113
+ *
114
+ * @param target - The target element (ref, function, or direct element)
115
+ * @param defaultElement - Default element to use if target is undefined
116
+ * @returns A stable reference that only changes when the actual target element changes
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * // For ref objects: returns the ref itself (stable)
121
+ * const ref = useRef<HTMLDivElement>(null)
122
+ * const key = useStableTarget(ref) // key === ref (stable)
123
+ *
124
+ * // For functions: returns the resolved actual element
125
+ * const key = useStableTarget(() => document) // key === document (stable)
126
+ *
127
+ * // For direct elements: returns the element itself
128
+ * const key = useStableTarget(divElement) // key === divElement (stable)
129
+ * ```
130
+ */ function useStableTarget(target, defaultElement) {
131
+ const targetRef = useRef(target);
132
+ targetRef.current = target;
133
+ // Calculate stable key without memoization
134
+ // For ref objects: return the ref itself (always stable)
135
+ // For functions/direct elements: resolve to the actual element
136
+ let stableKey;
137
+ if (!target) {
138
+ stableKey = defaultElement != null ? defaultElement : null;
139
+ } else if (typeof target === 'object' && 'current' in target) {
140
+ // Ref object - use the ref itself as the stable key
141
+ stableKey = target;
142
+ } else {
143
+ // Function or direct element - resolve to actual element
144
+ stableKey = getTargetElement(target, defaultElement);
145
+ }
146
+ return {
147
+ /** The stable key that can be safely used in effect dependencies */ key: stableKey,
148
+ /** A ref containing the current target (useful for accessing in effects) */ ref: targetRef
149
+ };
150
+ }
151
+
108
152
  function useEventListenerImpl(eventName, handler, element, options = defaultOptions$1) {
109
153
  const savedHandler = useLatest(handler);
110
- const targetElement = getTargetElement(element, defaultWindow);
154
+ const { key: elementKey, ref: elementRef } = useStableTarget(element, defaultWindow);
111
155
  useDeepCompareEffect(()=>{
156
+ // Call getTargetElement inside effect to support ref-based targets
157
+ // (ref.current is null during render, only available in commit phase)
158
+ const targetElement = getTargetElement(elementRef.current, defaultWindow);
112
159
  if (!(targetElement && targetElement.addEventListener)) {
113
160
  return;
114
161
  }
@@ -122,7 +169,7 @@ function useEventListenerImpl(eventName, handler, element, options = defaultOpti
122
169
  };
123
170
  }, [
124
171
  eventName,
125
- targetElement,
172
+ elementKey,
126
173
  options
127
174
  ]);
128
175
  }
@@ -1091,13 +1138,14 @@ const useDropZone = (target, onDrop)=>{
1091
1138
  const useResizeObserver = (target, callback, options = defaultOptions$1)=>{
1092
1139
  const savedCallback = useLatest(callback);
1093
1140
  const observerRef = useRef();
1141
+ const { key: targetKey, ref: targetRef } = useStableTarget(target);
1094
1142
  const stop = useCallback(()=>{
1095
1143
  if (observerRef.current) {
1096
1144
  observerRef.current.disconnect();
1097
1145
  }
1098
1146
  }, []);
1099
1147
  useDeepCompareEffect(()=>{
1100
- const element = getTargetElement(target);
1148
+ const element = getTargetElement(targetRef.current);
1101
1149
  if (!element) {
1102
1150
  return;
1103
1151
  }
@@ -1105,9 +1153,7 @@ const useResizeObserver = (target, callback, options = defaultOptions$1)=>{
1105
1153
  observerRef.current.observe(element, options);
1106
1154
  return stop;
1107
1155
  }, [
1108
- savedCallback,
1109
- stop,
1110
- target,
1156
+ targetKey,
1111
1157
  options
1112
1158
  ]);
1113
1159
  return stop;
@@ -1218,13 +1264,14 @@ const useElementSize = (target, options = defaultOptions$1)=>{
1218
1264
  const useIntersectionObserver = (target, callback, options = defaultOptions$1)=>{
1219
1265
  const savedCallback = useLatest(callback);
1220
1266
  const observerRef = useRef();
1267
+ const { key: targetKey, ref: targetRef } = useStableTarget(target);
1221
1268
  const stop = useCallback(()=>{
1222
1269
  if (observerRef.current) {
1223
1270
  observerRef.current.disconnect();
1224
1271
  }
1225
1272
  }, []);
1226
1273
  useDeepCompareEffect(()=>{
1227
- const element = getTargetElement(target);
1274
+ const element = getTargetElement(targetRef.current);
1228
1275
  if (!element) {
1229
1276
  return;
1230
1277
  }
@@ -1232,6 +1279,7 @@ const useIntersectionObserver = (target, callback, options = defaultOptions$1)=>
1232
1279
  observerRef.current.observe(element);
1233
1280
  return stop;
1234
1281
  }, [
1282
+ targetKey,
1235
1283
  options
1236
1284
  ]);
1237
1285
  return stop;
@@ -2337,13 +2385,14 @@ const useMousePressed = (target, options = defaultOptions$1)=>{
2337
2385
  const useMutationObserver = (callback, target, options = defaultOptions$1)=>{
2338
2386
  const callbackRef = useLatest(callback);
2339
2387
  const observerRef = useRef();
2388
+ const { key: targetKey, ref: targetRef } = useStableTarget(target);
2340
2389
  const stop = useCallback(()=>{
2341
2390
  if (observerRef.current) {
2342
2391
  observerRef.current.disconnect();
2343
2392
  }
2344
2393
  }, []);
2345
2394
  useDeepCompareEffect(()=>{
2346
- const element = getTargetElement(target);
2395
+ const element = getTargetElement(targetRef.current);
2347
2396
  if (!element) {
2348
2397
  return;
2349
2398
  }
@@ -2351,6 +2400,7 @@ const useMutationObserver = (callback, target, options = defaultOptions$1)=>{
2351
2400
  observerRef.current.observe(element, options);
2352
2401
  return stop;
2353
2402
  }, [
2403
+ targetKey,
2354
2404
  options
2355
2405
  ]);
2356
2406
  return stop;
@@ -3165,21 +3215,25 @@ const useSetState = (initialState)=>{
3165
3215
 
3166
3216
  function useSticky(targetElement, { axis = 'y', nav = 0 }, scrollElement) {
3167
3217
  const [isSticky, setSticky] = useState(false);
3218
+ const { key: targetKey, ref: targetRef } = useStableTarget(targetElement);
3219
+ const { key: scrollKey, ref: scrollRef } = useStableTarget(scrollElement);
3220
+ const axisRef = useLatest(axis);
3221
+ const navRef = useLatest(nav);
3168
3222
  const { run: scrollHandler } = useThrottleFn(()=>{
3169
- const element = getTargetElement(targetElement);
3223
+ const element = getTargetElement(targetRef.current);
3170
3224
  if (!element) {
3171
3225
  return;
3172
3226
  }
3173
3227
  const rect = element.getBoundingClientRect();
3174
- if (axis === 'y') {
3175
- setSticky((rect == null ? void 0 : rect.top) <= nav);
3228
+ if (axisRef.current === 'y') {
3229
+ setSticky((rect == null ? void 0 : rect.top) <= navRef.current);
3176
3230
  } else {
3177
- setSticky((rect == null ? void 0 : rect.left) <= nav);
3231
+ setSticky((rect == null ? void 0 : rect.left) <= navRef.current);
3178
3232
  }
3179
3233
  }, 50);
3180
3234
  useEffect(()=>{
3181
- const element = getTargetElement(targetElement);
3182
- const scrollParent = getTargetElement(scrollElement) || getScrollParent(axis, element);
3235
+ const element = getTargetElement(targetRef.current);
3236
+ const scrollParent = getTargetElement(scrollRef.current) || getScrollParent(axisRef.current, element);
3183
3237
  if (!element || !scrollParent) {
3184
3238
  return;
3185
3239
  }
@@ -3189,9 +3243,8 @@ function useSticky(targetElement, { axis = 'y', nav = 0 }, scrollElement) {
3189
3243
  scrollParent.removeEventListener('scroll', scrollHandler);
3190
3244
  };
3191
3245
  }, [
3192
- axis,
3193
- targetElement,
3194
- scrollElement,
3246
+ targetKey,
3247
+ scrollKey,
3195
3248
  scrollHandler
3196
3249
  ]);
3197
3250
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reactuses/core",
3
- "version": "6.1.10",
3
+ "version": "6.1.11",
4
4
  "license": "Unlicense",
5
5
  "homepage": "https://www.reactuse.com/",
6
6
  "repository": {