@fringeworks/react-utils 0.9.2 → 0.11.0

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.
Files changed (105) hide show
  1. package/README.ja.md +191 -11
  2. package/README.md +204 -24
  3. package/{_helpers → _internal}/_traverseChildren.cjs +1 -1
  4. package/{_helpers → _internal}/_traverseChildren.d.cts +1 -1
  5. package/{_helpers → _internal}/_traverseChildren.d.mts +1 -1
  6. package/{_helpers → _internal}/_traverseChildren.mjs +1 -1
  7. package/_internal/_useChangeHandler.cjs +48 -0
  8. package/_internal/_useChangeHandler.d.cts +18 -0
  9. package/_internal/_useChangeHandler.d.mts +18 -0
  10. package/_internal/_useChangeHandler.mjs +47 -0
  11. package/_internal/_useExecutionHandler.cjs +38 -0
  12. package/_internal/_useExecutionHandler.d.cts +20 -0
  13. package/_internal/_useExecutionHandler.d.mts +20 -0
  14. package/_internal/_useExecutionHandler.mjs +37 -0
  15. package/hooks/index.cjs +26 -2
  16. package/hooks/index.d.cts +13 -3
  17. package/hooks/index.d.mts +13 -3
  18. package/hooks/index.mjs +19 -3
  19. package/hooks/useChangeEffect/index.cjs +5 -0
  20. package/hooks/useChangeEffect/index.d.cts +3 -0
  21. package/hooks/useChangeEffect/index.d.mts +3 -0
  22. package/hooks/useChangeEffect/index.mjs +3 -0
  23. package/hooks/useChangeEffect/types.cjs +1 -0
  24. package/hooks/useChangeEffect/types.d.cts +11 -0
  25. package/hooks/useChangeEffect/types.d.mts +11 -0
  26. package/hooks/useChangeEffect/types.mjs +1 -0
  27. package/hooks/useChangeEffect/useChangeEffect.cjs +37 -0
  28. package/hooks/useChangeEffect/useChangeEffect.d.cts +27 -0
  29. package/hooks/useChangeEffect/useChangeEffect.d.mts +27 -0
  30. package/hooks/useChangeEffect/useChangeEffect.mjs +36 -0
  31. package/hooks/useChangeLayoutEffect/index.cjs +5 -0
  32. package/hooks/useChangeLayoutEffect/index.d.cts +2 -0
  33. package/hooks/useChangeLayoutEffect/index.d.mts +2 -0
  34. package/hooks/useChangeLayoutEffect/index.mjs +3 -0
  35. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.cjs +24 -0
  36. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.d.cts +14 -0
  37. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.d.mts +14 -0
  38. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.mjs +23 -0
  39. package/hooks/useExecutionEffect/index.cjs +5 -0
  40. package/hooks/useExecutionEffect/index.d.cts +2 -0
  41. package/hooks/useExecutionEffect/index.d.mts +2 -0
  42. package/hooks/useExecutionEffect/index.mjs +3 -0
  43. package/hooks/useExecutionEffect/useExecutionEffect.cjs +39 -0
  44. package/hooks/useExecutionEffect/useExecutionEffect.d.cts +30 -0
  45. package/hooks/useExecutionEffect/useExecutionEffect.d.mts +30 -0
  46. package/hooks/useExecutionEffect/useExecutionEffect.mjs +38 -0
  47. package/hooks/useExecutionLayoutEffect/index.cjs +5 -0
  48. package/hooks/useExecutionLayoutEffect/index.d.cts +2 -0
  49. package/hooks/useExecutionLayoutEffect/index.d.mts +2 -0
  50. package/hooks/useExecutionLayoutEffect/index.mjs +3 -0
  51. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.cjs +24 -0
  52. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.d.cts +15 -0
  53. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.d.mts +15 -0
  54. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.mjs +23 -0
  55. package/hooks/useExecutionMark/index.cjs +5 -0
  56. package/hooks/useExecutionMark/index.d.cts +3 -0
  57. package/hooks/useExecutionMark/index.d.mts +3 -0
  58. package/hooks/useExecutionMark/index.mjs +3 -0
  59. package/hooks/useExecutionMark/types.cjs +1 -0
  60. package/hooks/useExecutionMark/types.d.cts +15 -0
  61. package/hooks/useExecutionMark/types.d.mts +15 -0
  62. package/hooks/useExecutionMark/types.mjs +1 -0
  63. package/hooks/useExecutionMark/useExecutionMark.cjs +29 -0
  64. package/hooks/useExecutionMark/useExecutionMark.d.cts +19 -0
  65. package/hooks/useExecutionMark/useExecutionMark.d.mts +19 -0
  66. package/hooks/useExecutionMark/useExecutionMark.mjs +28 -0
  67. package/hooks/useIsHydrated/index.cjs +5 -0
  68. package/hooks/useIsHydrated/index.d.cts +2 -0
  69. package/hooks/useIsHydrated/index.d.mts +2 -0
  70. package/hooks/useIsHydrated/index.mjs +3 -0
  71. package/hooks/useIsHydrated/useIsHydrated.cjs +28 -0
  72. package/hooks/useIsHydrated/useIsHydrated.d.cts +15 -0
  73. package/hooks/useIsHydrated/useIsHydrated.d.mts +15 -0
  74. package/hooks/useIsHydrated/useIsHydrated.mjs +27 -0
  75. package/hooks/useRerender/index.cjs +5 -0
  76. package/hooks/useRerender/index.d.cts +2 -0
  77. package/hooks/useRerender/index.d.mts +2 -0
  78. package/hooks/useRerender/index.mjs +3 -0
  79. package/hooks/useRerender/useRerender.cjs +19 -0
  80. package/hooks/useRerender/useRerender.d.cts +11 -0
  81. package/hooks/useRerender/useRerender.d.mts +11 -0
  82. package/hooks/useRerender/useRerender.mjs +18 -0
  83. package/hooks/useStableCallback/index.cjs +5 -0
  84. package/hooks/useStableCallback/index.d.cts +2 -0
  85. package/hooks/useStableCallback/index.d.mts +2 -0
  86. package/hooks/useStableCallback/index.mjs +3 -0
  87. package/hooks/useStableCallback/useStableCallback.cjs +25 -0
  88. package/hooks/useStableCallback/useStableCallback.d.cts +15 -0
  89. package/hooks/useStableCallback/useStableCallback.d.mts +15 -0
  90. package/hooks/useStableCallback/useStableCallback.mjs +24 -0
  91. package/index.cjs +17 -1
  92. package/index.d.cts +13 -3
  93. package/index.d.mts +13 -3
  94. package/index.mjs +10 -2
  95. package/package.json +81 -1
  96. package/utils/forEachContent/forEachContent.cjs +2 -2
  97. package/utils/forEachContent/forEachContent.mjs +1 -1
  98. package/utils/forEachElement/forEachElement.cjs +2 -2
  99. package/utils/forEachElement/forEachElement.mjs +1 -1
  100. package/utils/markAt/types.d.cts +3 -0
  101. package/utils/markAt/types.d.mts +3 -0
  102. package/utils/transformContent/transformContent.cjs +2 -2
  103. package/utils/transformContent/transformContent.mjs +1 -1
  104. package/utils/transformElement/transformElement.cjs +2 -2
  105. package/utils/transformElement/transformElement.mjs +1 -1
package/README.ja.md CHANGED
@@ -31,15 +31,23 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
31
31
 
32
32
  ### hook
33
33
 
34
- | hook | 説明 |
35
- | ----------------------------------- | ---------------------------------------------------------------------- |
36
- | `useCombinedRefs(...refs)` | 複数の ref にまとめて値を渡す callback ref を返す |
37
- | `useConstant(factory)` | 初回のレンダリングで作った値を、アンマウントされるまで返し続ける |
38
- | `useExecutionAt(options?)` | Execution Request プロパティに渡す値と、実行のきっかけになる関数を返す |
39
- | `useIsMounted()` | マウントされているかどうかを判定する関数を返す |
40
- | `useLatestRef(value)` | 最新の値を保持する参照を返す |
41
- | `usePreviousValue(value, options?)` | 前回の値を返す |
42
- | `useStableLength(arr)` | 配列の要素数がレンダリングをまたいで変わらないことを確認する |
34
+ | hook | 説明 |
35
+ | ----------------------------------------------- | ------------------------------------------------------------------------ |
36
+ | `useChangeEffect(dependencies, callback)` | `xxxOn` のプロパティで受け取った依存の値が変わったときに、処理を実行する |
37
+ | `useChangeLayoutEffect(dependencies, callback)` | `useChangeEffect` の、画面に表示する前に実行する版 |
38
+ | `useCombinedRefs(...refs)` | 複数の ref にまとめて値を渡す callback ref を返す |
39
+ | `useConstant(factory)` | 初回のレンダリングで作った値を、アンマウントされるまで返し続ける |
40
+ | `useExecutionAt(options?)` | Execution Request プロパティに渡す値と、実行のきっかけになる関数を返す |
41
+ | `useExecutionEffect(request, callback)` | Execution Request プロパティで要求されたときに、処理を実行する |
42
+ | `useExecutionLayoutEffect(request, callback)` | `useExecutionEffect` の、画面に表示する前に実行する版 |
43
+ | `useExecutionMark()` | payload の無い Execution Request と、要求する関数を返す |
44
+ | `useIsHydrated()` | ハイドレーションが終わっているかを返す |
45
+ | `useIsMounted()` | マウントされているかどうかを判定する関数を返す |
46
+ | `useLatestRef(value)` | 最新の値を保持する参照を返す |
47
+ | `usePreviousValue(value, options?)` | 前回の値を返す |
48
+ | `useRerender()` | 再レンダリングだけを起こす関数を返す |
49
+ | `useStableCallback(callback)` | 参照が変わらず、最新のコールバックを呼ぶ関数を返す |
50
+ | `useStableLength(arr)` | 配列の要素数がレンダリングをまたいで変わらないことを確認する |
43
51
 
44
52
  ### 関数
45
53
 
@@ -56,6 +64,59 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
56
64
  | `transformContent(children, callback)` | Fragment を透過し、エレメント・文字列・数値を変換した配列を返す |
57
65
  | `transformElement(children, callback)` | Fragment を透過し、エレメントを変換した配列を返す |
58
66
 
67
+ ### `useChangeEffect`
68
+
69
+ ```ts
70
+ useChangeEffect(
71
+ dependencies: ChangeDependencies | undefined,
72
+ callback: () => void,
73
+ ): void
74
+ ```
75
+
76
+ `xxxOn` のプロパティ(例: `refreshOn`)を受け取る側の hook です。受け取った依存の値(Change Dependencies)が変わったときに、`callback` を実行します。処理は画面に表示した後(`useEffect`)に実行します。
77
+
78
+ 測り直しや再計算など、結果が入力だけで決まる操作を、利用者が渡した値の変化に合わせて実行するために使います。回数や payload に意味がある命令は、Execution Request(`useExecutionAt`・`useExecutionEffect`)を使います。
79
+
80
+ - 1段目のプロパティを `Object.is` で比べ、どれかが変わったときに実行します。プロパティが増えた・減った場合も変化とみなします。
81
+ - 比べるのは1段目だけです。プロパティの値がオブジェクトの場合は参照で比べるため、その場で作ったオブジェクト(`{ style }` など)を入れると、毎回変化とみなします。
82
+ - 依存の値そのものは、その場で書いたオブジェクトを渡してかまいません。
83
+ - 初回(マウントしたとき)は実行しません。
84
+ - `dependencies` が `undefined` の場合は、プロパティの無いオブジェクトとして扱います。省略できるプロパティをそのまま渡せます。
85
+ - `callback` は最新のものを呼びます。`callback` の参照が変わっても実行しません。
86
+ - Execution Request(`useExecutionAt` の値)をプロパティに入れると、要求のたびに実行します。
87
+
88
+ ```tsx
89
+ // 渡す側
90
+ <WrappedComponent refreshOn={{ className, theme }} />;
91
+
92
+ // 受け取る側
93
+ type Props = { refreshOn?: ChangeDependencies };
94
+
95
+ function WrappedComponent({ refreshOn }: Props) {
96
+ const instance = useConstant(() => createInstance());
97
+ useChangeEffect(refreshOn, () => {
98
+ instance.refresh();
99
+ });
100
+ // ...
101
+ }
102
+ ```
103
+
104
+ | 引数 | 型 | 説明 |
105
+ | -------------- | --------------------------------- | ------------------------------------ |
106
+ | `dependencies` | `ChangeDependencies \| undefined` | 受け取った依存の値 |
107
+ | `callback` | `() => void` | 依存の値が変わったときに実行する処理 |
108
+
109
+ ### `useChangeLayoutEffect`
110
+
111
+ ```ts
112
+ useChangeLayoutEffect(
113
+ dependencies: ChangeDependencies | undefined,
114
+ callback: () => void,
115
+ ): void
116
+ ```
117
+
118
+ `useChangeEffect` の、処理を画面に表示する前(`useLayoutEffect`)に実行する版です。測り直しなど、結果を画面に表示する前に反映したい処理に使います。依存の値の扱いと引数は `useChangeEffect` と同じです。
119
+
59
120
  ### `useCombinedRefs`
60
121
 
61
122
  ```ts
@@ -96,7 +157,7 @@ useExecutionAt<P = void>(
96
157
 
97
158
  命令的な API を持つ部品をラップしたコンポーネントで、プロパティを通じてそのメソッドを実行するための hook です。Execution Request プロパティ(`undoAt` のように `At` で終わるプロパティ)に渡す値と、実行のきっかけになる関数を返します。
98
159
 
99
- - `request.at` は初期状態では `0` で、`execute` を呼ぶたびに増加します。受け取る側は `at` が変わったら処理を実行します。
160
+ - `request.at` は初期状態では `0` で、`execute` を呼ぶたびに増加します。受け取る側は `useExecutionEffect` / `useExecutionLayoutEffect` で処理を実行します。payload が要らない場合は `useExecutionMark` を使います。
100
161
  - `execute` の参照は常に同じです。
101
162
 
102
163
  ```tsx
@@ -110,6 +171,90 @@ const [undoAt, undo] = useExecutionAt<string>();
110
171
  | ----------------- | --- | --------------------------------- |
111
172
  | `initialPayload?` | `P` | 初期状態の `request.payload` の値 |
112
173
 
174
+ ### `useExecutionEffect`
175
+
176
+ ```ts
177
+ useExecutionEffect<P = void>(
178
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
179
+ callback: (payload: P | undefined) => void,
180
+ ): void
181
+ ```
182
+
183
+ `useExecutionAt`・`useExecutionMark` と対になる、受け取る側の hook です。Execution Request プロパティで要求されたときに、要求の `payload` を渡して `callback` を実行します。処理は画面に表示した後(`useEffect`)に実行します。
184
+
185
+ - 印(`request.at`、または `ExecutionMark` の値)が変わったときだけ実行します。`payload` は比べません。
186
+ - マウントしたときに渡されていた要求は実行しません(マウントしたときの印を基準にします)。
187
+ - 印が `0`(まだ要求していない)の場合は実行しません。
188
+ - `ExecutionMark` を受け取った場合、`callback` の `payload` は `undefined` です。印は `markAt()` で作ったものでなくてもかまいません(要求のたびに異なる値であればよい)。
189
+ - 次のレンダリングまでに複数回要求した場合は、最後の要求だけを実行します。
190
+ - `callback` は最新のものを呼びます。`callback` の参照が変わっても実行しません。
191
+ - `request` が `undefined` の場合は、要求なしとして扱います。省略できるプロパティをそのまま渡せます。
192
+
193
+ ```tsx
194
+ type Props = { undoAt?: ExecutionRequest<string> };
195
+
196
+ function WrappedImperativeComponent({ undoAt }: Props) {
197
+ const instance = useConstant(() => createImperativeComponent());
198
+ useExecutionEffect(undoAt, (payload) => {
199
+ instance.undo(payload);
200
+ });
201
+ // ...
202
+ }
203
+ ```
204
+
205
+ | 引数 | 型 | 説明 |
206
+ | ---------- | ----------------------------------- | ------------------------------------------- |
207
+ | `request` | `ExecutionRequest<P> \| undefined` | 受け取った Execution Request プロパティの値 |
208
+ | `callback` | `(payload: P \| undefined) => void` | 要求されたときに実行する処理 |
209
+
210
+ ### `useExecutionLayoutEffect`
211
+
212
+ ```ts
213
+ useExecutionLayoutEffect<P = void>(
214
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
215
+ callback: (payload: P | undefined) => void,
216
+ ): void
217
+ ```
218
+
219
+ `useExecutionEffect` の、処理を画面に表示する前(`useLayoutEffect`)に実行する版です。スクロールの位置を合わせる、計測し直すなど、結果を画面に表示する前に反映したい処理に使います。要求の扱いと引数は `useExecutionEffect` と同じです。
220
+
221
+ ### `useExecutionMark`
222
+
223
+ ```ts
224
+ useExecutionMark(): UseExecutionMarkResult
225
+ ```
226
+
227
+ `useExecutionAt` の payload を持たない版です。payload の無い Execution Request(`ExecutionMark`。実体は数値)と、要求する関数を返します。命令的な部品の `focus()`・`reset()` のような、引数の要らないメソッドをプロパティから実行する場合に使います。
228
+
229
+ - 値は初期状態では `0`(まだ要求していない)で、`execute` を呼ぶたびに `markAt()` で作った新しい値になります。
230
+ - `execute` の参照は常に同じです。
231
+ - この hook を使わずに、`ExecutionMark` を自分で作ってもかまいません(ストアや設定に置いた値など)。その場合は、**要求のたびに前回と異なる値にしてください**。同じ値を続けて渡すと、2回目は要求とみなしません。`markAt()` を使うと、呼ぶたびに異なる値になります。
232
+
233
+ ```tsx
234
+ const [focusAt, focus] = useExecutionMark();
235
+
236
+ <button onClick={focus}>Focus</button>
237
+ <WrappedImperativeComponent focusAt={focusAt} />
238
+ ```
239
+
240
+ ### `useIsHydrated`
241
+
242
+ ```ts
243
+ useIsHydrated(): boolean
244
+ ```
245
+
246
+ ハイドレーションが終わっているかを返します。クライアントにしか無い値(`window`・`localStorage`・画面の幅など)を、ハイドレーションの不一致を起こさずに描画するために使います。
247
+
248
+ - サーバーの描画とハイドレーション中は `false`、その後は `true` を返します。
249
+ - ハイドレーションの後にマウントしたコンポーネントや、クライアントだけで描画した場合は、最初から `true` を返します。
250
+ - `typeof window` による判定と違い、クライアントでもハイドレーション中は `false` になります。そのため、サーバーの HTML と食い違いません。
251
+
252
+ ```tsx
253
+ const isHydrated = useIsHydrated();
254
+
255
+ <p>{isHydrated ? navigator.userAgent : ''}</p>;
256
+ ```
257
+
113
258
  ### `useIsMounted`
114
259
 
115
260
  ```ts
@@ -151,6 +296,38 @@ usePreviousValue<T = unknown>(
151
296
  | `initialValue?` | `T` | 値が一度も変わっていない間に返す値 |
152
297
  | `isEqual?` | `(oldValue: T, newValue: T) => boolean` | 同じ値とみなす場合に `true` を返す比較関数。未指定の場合は `Object.is` |
153
298
 
299
+ ### `useRerender`
300
+
301
+ ```ts
302
+ useRerender(): () => void
303
+ ```
304
+
305
+ 再レンダリングだけを起こす関数を返します。値を持たずに描画し直したい場合(外部の状態を読み直して表示するときなど)に使います。
306
+
307
+ - 返す関数の参照は常に同じです。
308
+
309
+ ### `useStableCallback`
310
+
311
+ ```ts
312
+ useStableCallback<A extends unknown[], R>(
313
+ callback: (...args: A) => R,
314
+ ): (...args: A) => R
315
+ ```
316
+
317
+ 参照が変わらず、呼ぶと最新の `callback` を実行する関数を返します。子のコンポーネントに渡すイベントハンドラーの参照を変えずに、中身は常に最新にする場合に使います。
318
+
319
+ - 返す関数の参照は常に同じです。
320
+ - 最新の `callback` はコミット時に反映されます。レンダリング中には呼ばないでください(古い `callback` が呼ばれます)。イベントハンドラーやエフェクトの中から呼びます。
321
+ - React 19.2 の `useEffectEvent` はエフェクトの中から呼ぶ専用ですが、この hook が返す関数は子のコンポーネントにも渡せます。
322
+
323
+ ```tsx
324
+ const handleSelect = useStableCallback((id: string) => {
325
+ onSelect(id, filter);
326
+ });
327
+
328
+ <MemoizedList onSelect={handleSelect} />;
329
+ ```
330
+
154
331
  ### `useStableLength`
155
332
 
156
333
  ```ts
@@ -261,7 +438,7 @@ HOC の `displayName` などに使う、コンポーネントの名前を返し
261
438
  markAt(): MarkAtResult
262
439
  ```
263
440
 
264
- 呼び出すたびに増加する値を返します。値は `1` から始まり、重複しません。`useExecutionAt` で `request.at` に使っています。
441
+ 呼び出すたびに増加する値を返します。値は `1` から始まり、重複しません。`useExecutionAt` の `request.at` と、`useExecutionMark` の値に使っています。
265
442
 
266
443
  ### `setRefCurrent`
267
444
 
@@ -304,15 +481,18 @@ const items = transformElement(children, (element, index) => (
304
481
  | 型 | 説明 |
305
482
  | ---------------------------- | -------------------------------------------------------------------------------- |
306
483
  | `AppliedProps<P, Ds>` | `applyDefaultProps` の戻り値の型 |
484
+ | `ChangeDependencies` | `xxxOn` のプロパティに渡す依存の値(1段目のプロパティを `Object.is` で比べる) |
307
485
  | `CSSPropertiesWithVars` | CSS 変数(`--name`)を書ける `CSSProperties` |
308
486
  | `ExecutionFunction<P>` | `useExecutionAt` が返す、実行のきっかけになる関数 |
309
487
  | `ExecutionRequest<P>` | `useExecutionAt` が返す、Execution Request プロパティに渡す値(`at`・`payload`) |
310
488
  | `ExtendableProps` | `extendElement` に渡せるエレメントの props(`className`・`style`) |
311
489
  | `LatestRef<T>` | `useLatestRef` が返す参照 |
490
+ | `ExecutionMark` | payload の無い Execution Request(実体は数値。要求のたびに異なる値にする) |
312
491
  | `MarkAtResult` | `markAt` の戻り値の型 |
313
492
  | `SkippedDefaults` | `applyDefaultProps` でスキップされる値(`null`・`undefined`・`false`) |
314
493
  | `UseExecutionAtOptions<P>` | `useExecutionAt` のオプション |
315
494
  | `UseExecutionAtResult<P>` | `useExecutionAt` の戻り値の型 |
495
+ | `UseExecutionMarkResult` | `useExecutionMark` の戻り値の型 |
316
496
  | `UsePreviousValueOptions<T>` | `usePreviousValue` のオプション |
317
497
 
318
498
  ## ライセンス
package/README.md CHANGED
@@ -31,15 +31,23 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
31
31
 
32
32
  ### Hooks
33
33
 
34
- | Hook | Description |
35
- | ----------------------------------- | ----------------------------------------------------------------------------- |
36
- | `useCombinedRefs(...refs)` | Returns a callback ref that passes the value to all the given refs |
37
- | `useConstant(factory)` | Keeps returning the value created on the first render until unmount |
38
- | `useExecutionAt(options?)` | Returns a value for an Execution Request prop and a function that triggers it |
39
- | `useIsMounted()` | Returns a function that tells whether the component is mounted |
40
- | `useLatestRef(value)` | Returns a ref that holds the latest value |
41
- | `usePreviousValue(value, options?)` | Returns the previous value |
42
- | `useStableLength(arr)` | Ensures that the length of an array does not change across renders |
34
+ | Hook | Description |
35
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------ |
36
+ | `useChangeEffect(dependencies, callback)` | Runs an operation when the dependency values received through an `xxxOn` prop change |
37
+ | `useChangeLayoutEffect(dependencies, callback)` | A version of `useChangeEffect` that runs before the browser paints |
38
+ | `useCombinedRefs(...refs)` | Returns a callback ref that passes the value to all the given refs |
39
+ | `useConstant(factory)` | Keeps returning the value created on the first render until unmount |
40
+ | `useExecutionAt(options?)` | Returns a value for an Execution Request prop and a function that triggers it |
41
+ | `useExecutionEffect(request, callback)` | Runs an operation when it is requested through an Execution Request prop |
42
+ | `useExecutionLayoutEffect(request, callback)` | A version of `useExecutionEffect` that runs before the browser paints |
43
+ | `useExecutionMark()` | Returns an Execution Request without a payload and a function that makes a request |
44
+ | `useIsHydrated()` | Returns whether hydration has finished |
45
+ | `useIsMounted()` | Returns a function that tells whether the component is mounted |
46
+ | `useLatestRef(value)` | Returns a ref that holds the latest value |
47
+ | `usePreviousValue(value, options?)` | Returns the previous value |
48
+ | `useRerender()` | Returns a function that only triggers a re-render |
49
+ | `useStableCallback(callback)` | Returns a function with a stable reference that calls the latest callback |
50
+ | `useStableLength(arr)` | Ensures that the length of an array does not change across renders |
43
51
 
44
52
  ### Functions
45
53
 
@@ -56,6 +64,59 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
56
64
  | `transformContent(children, callback)` | Maps elements, strings, and numbers to an array, looking through Fragments |
57
65
  | `transformElement(children, callback)` | Maps elements to an array, looking through Fragments |
58
66
 
67
+ ### `useChangeEffect`
68
+
69
+ ```ts
70
+ useChangeEffect(
71
+ dependencies: ChangeDependencies | undefined,
72
+ callback: () => void,
73
+ ): void
74
+ ```
75
+
76
+ The receiving hook for `xxxOn` props (such as `refreshOn`). It runs `callback` when the received dependency values (Change Dependencies) change. The operation runs after the browser paints (`useEffect`).
77
+
78
+ Use it to run operations whose result depends only on their inputs, such as measuring or recalculating again, when the values passed by the user change. For commands where the count or the payload matters, use Execution Requests (`useExecutionAt`, `useExecutionEffect`).
79
+
80
+ - It compares the first-level properties with `Object.is` and runs when any of them changes. Added or removed properties also count as a change.
81
+ - Only the first level is compared. Object values are compared by reference, so putting an object created during rendering (such as `{ style }`) counts as a change every time.
82
+ - The dependency object itself may be written inline.
83
+ - It does not run on the first commit (mount).
84
+ - An `undefined` `dependencies` is treated as an object with no properties, so an optional prop can be passed as is.
85
+ - The latest `callback` is called. Changing the reference of `callback` does not run it.
86
+ - Putting an Execution Request (the value of `useExecutionAt`) in a property runs the operation on every request.
87
+
88
+ ```tsx
89
+ // Passing side
90
+ <WrappedComponent refreshOn={{ className, theme }} />;
91
+
92
+ // Receiving side
93
+ type Props = { refreshOn?: ChangeDependencies };
94
+
95
+ function WrappedComponent({ refreshOn }: Props) {
96
+ const instance = useConstant(() => createInstance());
97
+ useChangeEffect(refreshOn, () => {
98
+ instance.refresh();
99
+ });
100
+ // ...
101
+ }
102
+ ```
103
+
104
+ | Parameter | Type | Description |
105
+ | -------------- | --------------------------------- | ------------------------------------------------------ |
106
+ | `dependencies` | `ChangeDependencies \| undefined` | The received dependency values |
107
+ | `callback` | `() => void` | The operation to run when the dependency values change |
108
+
109
+ ### `useChangeLayoutEffect`
110
+
111
+ ```ts
112
+ useChangeLayoutEffect(
113
+ dependencies: ChangeDependencies | undefined,
114
+ callback: () => void,
115
+ ): void
116
+ ```
117
+
118
+ A version of `useChangeEffect` that runs the operation before the browser paints (`useLayoutEffect`). Use it for operations whose result should be reflected before painting, such as measuring again. Dependency values and parameters are handled the same way as `useChangeEffect`.
119
+
59
120
  ### `useCombinedRefs`
60
121
 
61
122
  ```ts
@@ -96,7 +157,7 @@ useExecutionAt<P = void>(
96
157
 
97
158
  A hook for components that wrap a part with an imperative API, letting you call its methods through props. It returns a value to pass to an Execution Request prop (a prop ending with `At`, such as `undoAt`) and a function that triggers it.
98
159
 
99
- - `request.at` is `0` initially and increases every time `execute` is called. The receiving side runs the operation when `at` changes.
160
+ - `request.at` is `0` initially and increases every time `execute` is called. The receiving side runs the operation with `useExecutionEffect` / `useExecutionLayoutEffect`. Use `useExecutionMark` when no payload is needed.
100
161
  - `execute` always keeps the same reference.
101
162
 
102
163
  ```tsx
@@ -110,6 +171,90 @@ const [undoAt, undo] = useExecutionAt<string>();
110
171
  | ----------------- | ---- | -------------------------------------- |
111
172
  | `initialPayload?` | `P` | The initial value of `request.payload` |
112
173
 
174
+ ### `useExecutionEffect`
175
+
176
+ ```ts
177
+ useExecutionEffect<P = void>(
178
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
179
+ callback: (payload: P | undefined) => void,
180
+ ): void
181
+ ```
182
+
183
+ The receiving counterpart of `useExecutionAt` and `useExecutionMark`. When an operation is requested through an Execution Request prop, it runs `callback` with the request's `payload`. The operation runs after the browser paints (`useEffect`).
184
+
185
+ - It runs only when the mark (`request.at`, or the value of an `ExecutionMark`) changes. `payload` is not compared.
186
+ - A request that was already passed at mount is not run (the mark at mount is the baseline).
187
+ - It does not run when the mark is `0` (not requested yet).
188
+ - When it receives an `ExecutionMark`, the `payload` passed to `callback` is `undefined`. The mark does not have to be created by `markAt()` (any value that differs on every request works).
189
+ - If multiple requests are made before the next render, only the last one is run.
190
+ - The latest `callback` is called. Changing the reference of `callback` does not run it.
191
+ - An `undefined` `request` is treated as no request, so an optional prop can be passed as is.
192
+
193
+ ```tsx
194
+ type Props = { undoAt?: ExecutionRequest<string> };
195
+
196
+ function WrappedImperativeComponent({ undoAt }: Props) {
197
+ const instance = useConstant(() => createImperativeComponent());
198
+ useExecutionEffect(undoAt, (payload) => {
199
+ instance.undo(payload);
200
+ });
201
+ // ...
202
+ }
203
+ ```
204
+
205
+ | Parameter | Type | Description |
206
+ | ---------- | ----------------------------------- | ------------------------------------------------ |
207
+ | `request` | `ExecutionRequest<P> \| undefined` | The value of the received Execution Request prop |
208
+ | `callback` | `(payload: P \| undefined) => void` | The operation to run when requested |
209
+
210
+ ### `useExecutionLayoutEffect`
211
+
212
+ ```ts
213
+ useExecutionLayoutEffect<P = void>(
214
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
215
+ callback: (payload: P | undefined) => void,
216
+ ): void
217
+ ```
218
+
219
+ A version of `useExecutionEffect` that runs the operation before the browser paints (`useLayoutEffect`). Use it for operations whose result should be reflected before painting, such as adjusting the scroll position or measuring again. Requests and parameters are handled the same way as `useExecutionEffect`.
220
+
221
+ ### `useExecutionMark`
222
+
223
+ ```ts
224
+ useExecutionMark(): UseExecutionMarkResult
225
+ ```
226
+
227
+ A version of `useExecutionAt` without a payload. It returns an Execution Request without a payload (`ExecutionMark`, which is a number) and a function that makes a request. Use it to run methods that take no arguments, such as `focus()` or `reset()` of an imperative part, through props.
228
+
229
+ - The value is `0` (not requested yet) initially, and becomes a new value created by `markAt()` every time `execute` is called.
230
+ - `execute` always keeps the same reference.
231
+ - You may create an `ExecutionMark` yourself without this hook (for example, a value in a store or configuration). In that case, **make it different from the previous value on every request**. Passing the same value again is not treated as a new request. `markAt()` returns a different value on every call.
232
+
233
+ ```tsx
234
+ const [focusAt, focus] = useExecutionMark();
235
+
236
+ <button onClick={focus}>Focus</button>
237
+ <WrappedImperativeComponent focusAt={focusAt} />
238
+ ```
239
+
240
+ ### `useIsHydrated`
241
+
242
+ ```ts
243
+ useIsHydrated(): boolean
244
+ ```
245
+
246
+ Returns whether hydration has finished. Use it to render client-only values (`window`, `localStorage`, the viewport width, and so on) without causing a hydration mismatch.
247
+
248
+ - Returns `false` during server rendering and hydration, and `true` afterwards.
249
+ - Returns `true` from the start in components mounted after hydration and in client-only rendering.
250
+ - Unlike checking `typeof window`, it returns `false` during hydration even on the client, so the output matches the server HTML.
251
+
252
+ ```tsx
253
+ const isHydrated = useIsHydrated();
254
+
255
+ <p>{isHydrated ? navigator.userAgent : ''}</p>;
256
+ ```
257
+
113
258
  ### `useIsMounted`
114
259
 
115
260
  ```ts
@@ -151,6 +296,38 @@ When the value changes, the previous value is updated by a state update during r
151
296
  | `initialValue?` | `T` | The value returned until the value changes for the first time |
152
297
  | `isEqual?` | `(oldValue: T, newValue: T) => boolean` | A comparison that returns `true` for values considered equal. Defaults to `Object.is` |
153
298
 
299
+ ### `useRerender`
300
+
301
+ ```ts
302
+ useRerender(): () => void
303
+ ```
304
+
305
+ Returns a function that only triggers a re-render. Use it when you want to render again without holding a value (for example, to show external state after reading it again).
306
+
307
+ - The returned function always keeps the same reference.
308
+
309
+ ### `useStableCallback`
310
+
311
+ ```ts
312
+ useStableCallback<A extends unknown[], R>(
313
+ callback: (...args: A) => R,
314
+ ): (...args: A) => R
315
+ ```
316
+
317
+ Returns a function whose reference never changes and that calls the latest `callback`. Use it to keep the reference of an event handler passed to child components while always running the latest logic.
318
+
319
+ - The returned function always keeps the same reference.
320
+ - The latest `callback` is applied on commit. Do not call the function during rendering (the previous `callback` would be called). Call it from event handlers or effects.
321
+ - React 19.2's `useEffectEvent` can only be called from effects, while the function returned by this hook can also be passed to child components.
322
+
323
+ ```tsx
324
+ const handleSelect = useStableCallback((id: string) => {
325
+ onSelect(id, filter);
326
+ });
327
+
328
+ <MemoizedList onSelect={handleSelect} />;
329
+ ```
330
+
154
331
  ### `useStableLength`
155
332
 
156
333
  ```ts
@@ -261,7 +438,7 @@ Returns the name of a component, for use in an HOC's `displayName` and the like.
261
438
  markAt(): MarkAtResult
262
439
  ```
263
440
 
264
- Returns a value that increases on every call. Values start from `1` and are never duplicated. `useExecutionAt` uses it for `request.at`.
441
+ Returns a value that increases on every call. Values start from `1` and are never duplicated. `useExecutionAt` uses it for `request.at`, and `useExecutionMark` uses it for its value.
265
442
 
266
443
  ### `setRefCurrent`
267
444
 
@@ -301,19 +478,22 @@ const items = transformElement(children, (element, index) => (
301
478
 
302
479
  ### Types
303
480
 
304
- | Type | Description |
305
- | ---------------------------- | -------------------------------------------------------------------------------------- |
306
- | `AppliedProps<P, Ds>` | The return type of `applyDefaultProps` |
307
- | `CSSPropertiesWithVars` | `CSSProperties` that also accepts CSS variables (`--name`) |
308
- | `ExecutionFunction<P>` | The trigger function returned by `useExecutionAt` |
309
- | `ExecutionRequest<P>` | The value for an Execution Request prop returned by `useExecutionAt` (`at`, `payload`) |
310
- | `ExtendableProps` | The props of an element that `extendElement` accepts (`className`, `style`) |
311
- | `LatestRef<T>` | The ref returned by `useLatestRef` |
312
- | `MarkAtResult` | The return type of `markAt` |
313
- | `SkippedDefaults` | Values skipped by `applyDefaultProps` (`null`, `undefined`, `false`) |
314
- | `UseExecutionAtOptions<P>` | The options of `useExecutionAt` |
315
- | `UseExecutionAtResult<P>` | The return type of `useExecutionAt` |
316
- | `UsePreviousValueOptions<T>` | The options of `usePreviousValue` |
481
+ | Type | Description |
482
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------ |
483
+ | `AppliedProps<P, Ds>` | The return type of `applyDefaultProps` |
484
+ | `ChangeDependencies` | The dependency values passed to an `xxxOn` prop (first-level properties are compared with `Object.is`) |
485
+ | `CSSPropertiesWithVars` | `CSSProperties` that also accepts CSS variables (`--name`) |
486
+ | `ExecutionFunction<P>` | The trigger function returned by `useExecutionAt` |
487
+ | `ExecutionRequest<P>` | The value for an Execution Request prop returned by `useExecutionAt` (`at`, `payload`) |
488
+ | `ExtendableProps` | The props of an element that `extendElement` accepts (`className`, `style`) |
489
+ | `LatestRef<T>` | The ref returned by `useLatestRef` |
490
+ | `ExecutionMark` | An Execution Request without a payload (a number; make it different on every request) |
491
+ | `MarkAtResult` | The return type of `markAt` |
492
+ | `SkippedDefaults` | Values skipped by `applyDefaultProps` (`null`, `undefined`, `false`) |
493
+ | `UseExecutionAtOptions<P>` | The options of `useExecutionAt` |
494
+ | `UseExecutionAtResult<P>` | The return type of `useExecutionAt` |
495
+ | `UseExecutionMarkResult` | The return type of `useExecutionMark` |
496
+ | `UsePreviousValueOptions<T>` | The options of `usePreviousValue` |
317
497
 
318
498
  ## License
319
499
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
2
  let react = require("react");
3
3
 
4
- //#region src/_helpers/_traverseChildren.ts
4
+ //#region src/_internal/_traverseChildren.ts
5
5
  /**
6
6
  * ReactElement、string、numberのいずれかであるかを判定する
7
7
  * @param child 子要素
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, ReactNode } from "react";
2
- //#region src/_helpers/_traverseChildren.d.ts
2
+ //#region src/_internal/_traverseChildren.d.ts
3
3
  type IndexCursor = {
4
4
  current: number;
5
5
  };
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, ReactNode } from "react";
2
- //#region src/_helpers/_traverseChildren.d.ts
2
+ //#region src/_internal/_traverseChildren.d.ts
3
3
  type IndexCursor = {
4
4
  current: number;
5
5
  };
@@ -1,6 +1,6 @@
1
1
  import { Children, Fragment, isValidElement } from "react";
2
2
 
3
- //#region src/_helpers/_traverseChildren.ts
3
+ //#region src/_internal/_traverseChildren.ts
4
4
  /**
5
5
  * ReactElement、string、numberのいずれかであるかを判定する
6
6
  * @param child 子要素
@@ -0,0 +1,48 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ const require_hooks_useLatestRef_useLatestRef = require("../hooks/useLatestRef/useLatestRef.cjs");
3
+ require("../hooks/useLatestRef/index.cjs");
4
+ let react = require("react");
5
+
6
+ //#region src/_internal/_useChangeHandler.ts
7
+ /**
8
+ * 依存の値が変わったか(1段目のプロパティを `Object.is` で比べる)
9
+ *
10
+ * undefined はプロパティの無いオブジェクトとして扱う
11
+ */
12
+ const isChanged = (prev, next) => {
13
+ if (prev === next) return false;
14
+ const a = prev ?? {};
15
+ const b = next ?? {};
16
+ const keys = Object.keys(a);
17
+ if (keys.length !== Object.keys(b).length) return true;
18
+ return keys.some((key) => !Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key]));
19
+ };
20
+ /**
21
+ * 依存の値(Change Dependencies)を受け取り、変わっていれば処理を実行する関数を返すフック
22
+ *
23
+ * `useChangeEffect`・`useChangeLayoutEffect` に共通の処理。返す関数を、毎回のコミットでエフェクトから呼ぶ。
24
+ *
25
+ * - 前回の処理の後にコミットした依存の値と比べる。初回のコミットの値が最初の基準になるため、初回は実行しない
26
+ * - 比べるのはエフェクトの中(コミットした値どうし)。破棄されたレンダリングの値とは比べない
27
+ * - 同じ値のままエフェクトが再実行されても(StrictMode など)実行しない
28
+ * - コールバックは最新のものを呼ぶ
29
+ *
30
+ * @param dependencies 依存の値。undefined の場合は依存なし
31
+ * @param callback 依存の値が変わったときに実行する処理
32
+ * @returns エフェクトで呼ぶ関数。参照は常に同じ
33
+ */
34
+ function useChangeHandler(dependencies, callback) {
35
+ const dependenciesRef = require_hooks_useLatestRef_useLatestRef.default(dependencies);
36
+ const callbackRef = require_hooks_useLatestRef_useLatestRef.default(callback);
37
+ const committedRef = (0, react.useRef)(dependencies);
38
+ return (0, react.useCallback)(() => {
39
+ const next = dependenciesRef.current;
40
+ if (isChanged(committedRef.current, next)) {
41
+ committedRef.current = next;
42
+ callbackRef.current();
43
+ }
44
+ }, [dependenciesRef, callbackRef]);
45
+ }
46
+
47
+ //#endregion
48
+ exports.default = useChangeHandler;
@@ -0,0 +1,18 @@
1
+ import { ChangeDependencies } from "../hooks/useChangeEffect/types.cjs";
2
+ //#region src/_internal/_useChangeHandler.d.ts
3
+ /**
4
+ * 依存の値(Change Dependencies)を受け取り、変わっていれば処理を実行する関数を返すフック
5
+ *
6
+ * `useChangeEffect`・`useChangeLayoutEffect` に共通の処理。返す関数を、毎回のコミットでエフェクトから呼ぶ。
7
+ *
8
+ * - 前回の処理の後にコミットした依存の値と比べる。初回のコミットの値が最初の基準になるため、初回は実行しない
9
+ * - 比べるのはエフェクトの中(コミットした値どうし)。破棄されたレンダリングの値とは比べない
10
+ * - 同じ値のままエフェクトが再実行されても(StrictMode など)実行しない
11
+ * - コールバックは最新のものを呼ぶ
12
+ *
13
+ * @param dependencies 依存の値。undefined の場合は依存なし
14
+ * @param callback 依存の値が変わったときに実行する処理
15
+ * @returns エフェクトで呼ぶ関数。参照は常に同じ
16
+ */
17
+ declare function useChangeHandler(dependencies: ChangeDependencies | undefined, callback: () => void): () => void;
18
+ export = useChangeHandler;