use-good-hooks 1.0.41 → 1.0.43
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/README.md +22 -1
- package/dist/use-history-state.d.ts +0 -1
- package/dist/use-history-state.js +77 -83
- package/dist/use-unmount.d.ts +4 -0
- package/dist/use-unmount.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -245,7 +245,6 @@ const TextEditor = () => {
|
|
|
245
245
|
- `options`: (Optional) Configuration options:
|
|
246
246
|
- `debounceMs`: Time in milliseconds to debounce the state changes (default: 250)
|
|
247
247
|
- `debounceSettings`: Debounce settings object from Lodash
|
|
248
|
-
- `immutable`: Boolean indicating if the state should be treated as immutable (default: true)
|
|
249
248
|
- `maxCapacity`: Maximum number of history entries to keep (default: 10)
|
|
250
249
|
- `onChange`: Function to call when the state changes, receives `{ action, state }`
|
|
251
250
|
- `paused`: Boolean indicating if the history is paused (default: false)
|
|
@@ -609,6 +608,28 @@ const DelayedComponent = () => {
|
|
|
609
608
|
- `setLate`: A function to update the state after the specified delay. It can also accept a second boolean argument to update the state immediately.
|
|
610
609
|
- `cancel`: A function to cancel a pending state update.
|
|
611
610
|
|
|
611
|
+
### `useUnmount`
|
|
612
|
+
|
|
613
|
+
Runs a callback when the component unmounts for real. React's Strict Mode simulates an unmount right after mount (cleanup, then setup again on the same instance); a plain `useEffect` cleanup fires there too, which aborts in-flight requests or tears down subscriptions that the remount never rebuilds. `useUnmount` defers the callback by one microtask and cancels it if the effect is set up again, so only the real unmount reaches it. The latest callback is always the one called.
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import useUnmount from 'use-good-hooks/use-unmount';
|
|
617
|
+
|
|
618
|
+
const Subscriber = ({ channel }) => {
|
|
619
|
+
const controller = useRef(new AbortController());
|
|
620
|
+
|
|
621
|
+
useUnmount(() => {
|
|
622
|
+
controller.current.abort();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
return <p>Listening to {channel}</p>;
|
|
626
|
+
};
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
#### Parameters
|
|
630
|
+
|
|
631
|
+
- `fn`: The callback to run on unmount. It is called once, one microtask after the real unmount, and never on a Strict Mode remount.
|
|
632
|
+
|
|
612
633
|
## 🧪 Running Tests
|
|
613
634
|
|
|
614
635
|
This library is thoroughly tested with Vitest and React Testing Library. To run the tests:
|
|
@@ -29,7 +29,6 @@ declare type HistoryOnChange<T> = ({ action, state }: {
|
|
|
29
29
|
export declare type HistoryOptions<T> = {
|
|
30
30
|
debounceMs?: number;
|
|
31
31
|
debounceSettings?: DebounceSettings;
|
|
32
|
-
immutable?: boolean;
|
|
33
32
|
maxCapacity?: number;
|
|
34
33
|
onChange?: HistoryOnChange<T>;
|
|
35
34
|
paused?: boolean;
|
|
@@ -1,27 +1,24 @@
|
|
|
1
|
-
import { useRef as
|
|
2
|
-
import
|
|
3
|
-
import b from "lodash/
|
|
4
|
-
import
|
|
5
|
-
|
|
1
|
+
import { useRef as U, useReducer as I, useMemo as A, useEffect as T } from "react";
|
|
2
|
+
import h from "lodash/cloneDeep";
|
|
3
|
+
import b from "lodash/isEqual";
|
|
4
|
+
import C from "lodash/debounce";
|
|
5
|
+
import o from "lodash/size";
|
|
6
|
+
const L = {
|
|
6
7
|
future: [],
|
|
7
8
|
past: [],
|
|
8
9
|
present: null
|
|
9
|
-
},
|
|
10
|
-
const w =
|
|
10
|
+
}, p = (l) => h(l), M = (l, f) => b(l, f), v = (l, f) => {
|
|
11
|
+
const w = U(l), d = U(f ?? {}), [c, u] = I(
|
|
11
12
|
(n, e) => {
|
|
12
|
-
const {
|
|
13
|
-
immutable: u = !0,
|
|
14
|
-
maxCapacity: r = 10,
|
|
15
|
-
onChange: S = () => null
|
|
16
|
-
} = d.current, { future: R, past: y, paused: E, present: m } = n;
|
|
13
|
+
const { maxCapacity: y = 10, onChange: t = () => null } = d.current, { future: m, past: i, paused: S, present: E } = n;
|
|
17
14
|
if (e.type === "INITIAL") {
|
|
18
15
|
const s = {
|
|
19
16
|
future: [],
|
|
20
17
|
past: [],
|
|
21
|
-
paused:
|
|
22
|
-
present:
|
|
18
|
+
paused: S,
|
|
19
|
+
present: p(e.initialState)
|
|
23
20
|
};
|
|
24
|
-
return
|
|
21
|
+
return t({
|
|
25
22
|
action: e.type,
|
|
26
23
|
state: s.present
|
|
27
24
|
}), s;
|
|
@@ -32,18 +29,18 @@ const h = {
|
|
|
32
29
|
paused: !0
|
|
33
30
|
};
|
|
34
31
|
if (e.type === "REDO") {
|
|
35
|
-
if (
|
|
32
|
+
if (o(m) === 0)
|
|
36
33
|
return n;
|
|
37
|
-
const s =
|
|
38
|
-
future:
|
|
39
|
-
past: [...
|
|
40
|
-
paused:
|
|
41
|
-
present:
|
|
34
|
+
const s = m[0], a = {
|
|
35
|
+
future: m.slice(1),
|
|
36
|
+
past: [...i, p(E)],
|
|
37
|
+
paused: S,
|
|
38
|
+
present: p(s)
|
|
42
39
|
};
|
|
43
|
-
return
|
|
40
|
+
return t({
|
|
44
41
|
action: e.type,
|
|
45
|
-
state:
|
|
46
|
-
}),
|
|
42
|
+
state: a.present
|
|
43
|
+
}), a;
|
|
47
44
|
} else {
|
|
48
45
|
if (e.type === "RESUME")
|
|
49
46
|
return {
|
|
@@ -51,115 +48,112 @@ const h = {
|
|
|
51
48
|
paused: !1
|
|
52
49
|
};
|
|
53
50
|
if (e.type === "REPLACE") {
|
|
54
|
-
const { newPresent: s } = e,
|
|
51
|
+
const { newPresent: s } = e, r = {
|
|
55
52
|
...n,
|
|
56
53
|
future: [],
|
|
57
54
|
past: [],
|
|
58
55
|
present: s
|
|
59
56
|
};
|
|
60
|
-
return
|
|
57
|
+
return t({
|
|
61
58
|
action: e.type,
|
|
62
|
-
state:
|
|
63
|
-
}),
|
|
59
|
+
state: r.present
|
|
60
|
+
}), r;
|
|
64
61
|
} else if (e.type === "SET") {
|
|
65
62
|
const { newPresent: s } = e;
|
|
66
|
-
if (
|
|
63
|
+
if (M(s, E))
|
|
67
64
|
return n;
|
|
68
|
-
if (
|
|
69
|
-
const
|
|
65
|
+
if (S) {
|
|
66
|
+
const R = {
|
|
70
67
|
...n,
|
|
71
|
-
present:
|
|
68
|
+
present: p(s)
|
|
72
69
|
};
|
|
73
|
-
return
|
|
70
|
+
return t({
|
|
74
71
|
action: e.type,
|
|
75
|
-
state:
|
|
76
|
-
}),
|
|
72
|
+
state: R.present
|
|
73
|
+
}), R;
|
|
77
74
|
}
|
|
78
|
-
let
|
|
79
|
-
|
|
80
|
-
const
|
|
75
|
+
let r = [...i];
|
|
76
|
+
E !== null && (r = [...r, p(E)]), y > 0 && o(r) > y && (r = r.slice(o(r) - y));
|
|
77
|
+
const a = {
|
|
81
78
|
future: [],
|
|
82
|
-
past:
|
|
83
|
-
paused:
|
|
84
|
-
present:
|
|
79
|
+
past: r,
|
|
80
|
+
paused: S,
|
|
81
|
+
present: p(s)
|
|
85
82
|
};
|
|
86
|
-
return
|
|
83
|
+
return t({
|
|
87
84
|
action: e.type,
|
|
88
|
-
state:
|
|
89
|
-
}),
|
|
85
|
+
state: a.present
|
|
86
|
+
}), a;
|
|
90
87
|
} else if (e.type === "UNDO") {
|
|
91
|
-
if (
|
|
88
|
+
if (o(i) === 0)
|
|
92
89
|
return n;
|
|
93
|
-
const s =
|
|
94
|
-
future: [
|
|
95
|
-
past:
|
|
96
|
-
paused:
|
|
97
|
-
present:
|
|
90
|
+
const s = i[o(i) - 1], r = i.slice(0, o(i) - 1), a = {
|
|
91
|
+
future: [p(E), ...m],
|
|
92
|
+
past: r,
|
|
93
|
+
paused: S,
|
|
94
|
+
present: p(s)
|
|
98
95
|
};
|
|
99
|
-
return
|
|
96
|
+
return t({
|
|
100
97
|
action: e.type,
|
|
101
|
-
state:
|
|
102
|
-
}),
|
|
98
|
+
state: a.present
|
|
99
|
+
}), a;
|
|
103
100
|
} else
|
|
104
101
|
throw new Error("Unsupported action type");
|
|
105
102
|
}
|
|
106
103
|
}
|
|
107
104
|
},
|
|
108
105
|
{
|
|
109
|
-
...
|
|
106
|
+
...L,
|
|
110
107
|
paused: d.current.paused ?? !1,
|
|
111
|
-
present:
|
|
112
|
-
w.current,
|
|
113
|
-
d.current.immutable
|
|
114
|
-
)
|
|
108
|
+
present: p(w.current)
|
|
115
109
|
}
|
|
116
|
-
),
|
|
117
|
-
canRedo: c
|
|
118
|
-
canUndo: c
|
|
119
|
-
future:
|
|
120
|
-
past:
|
|
121
|
-
paused:
|
|
122
|
-
present:
|
|
123
|
-
}), [
|
|
124
|
-
const { debounceMs: n = 250, debounceSettings: e } = d.current,
|
|
125
|
-
(
|
|
110
|
+
), D = A(() => ({
|
|
111
|
+
canRedo: o(c.future) !== 0,
|
|
112
|
+
canUndo: o(c.past) !== 0,
|
|
113
|
+
future: c.future,
|
|
114
|
+
past: c.past,
|
|
115
|
+
paused: c.paused,
|
|
116
|
+
present: c.present
|
|
117
|
+
}), [c]), P = A(() => {
|
|
118
|
+
const { debounceMs: n = 250, debounceSettings: e } = d.current, y = C(
|
|
119
|
+
(t) => u({ type: "SET", newPresent: t }),
|
|
126
120
|
n,
|
|
127
121
|
e
|
|
128
122
|
);
|
|
129
123
|
return {
|
|
130
124
|
initial: () => {
|
|
131
|
-
|
|
125
|
+
u({
|
|
132
126
|
type: "INITIAL",
|
|
133
127
|
initialState: w.current
|
|
134
128
|
});
|
|
135
129
|
},
|
|
136
130
|
pause: () => {
|
|
137
|
-
|
|
131
|
+
u({ type: "PAUSE" });
|
|
138
132
|
},
|
|
139
133
|
redo: () => {
|
|
140
|
-
|
|
134
|
+
u({ type: "REDO" });
|
|
141
135
|
},
|
|
142
|
-
replace: (
|
|
143
|
-
|
|
136
|
+
replace: (t) => {
|
|
137
|
+
u({ type: "REPLACE", newPresent: t });
|
|
144
138
|
},
|
|
145
139
|
resume: () => {
|
|
146
|
-
|
|
140
|
+
u({ type: "RESUME" });
|
|
147
141
|
},
|
|
148
|
-
set: (
|
|
149
|
-
n ?
|
|
142
|
+
set: (t) => {
|
|
143
|
+
n ? y(t) : u({ type: "SET", newPresent: t });
|
|
150
144
|
},
|
|
151
|
-
setDirect: (
|
|
152
|
-
|
|
145
|
+
setDirect: (t) => {
|
|
146
|
+
u({ type: "SET", newPresent: t });
|
|
153
147
|
},
|
|
154
148
|
undo: () => {
|
|
155
|
-
|
|
149
|
+
u({ type: "UNDO" });
|
|
156
150
|
}
|
|
157
151
|
};
|
|
158
152
|
}, []);
|
|
159
|
-
return
|
|
160
|
-
|
|
161
|
-
}, [
|
|
153
|
+
return T(() => {
|
|
154
|
+
f && (d.current = f);
|
|
155
|
+
}, [f]), [D, P];
|
|
162
156
|
};
|
|
163
157
|
export {
|
|
164
|
-
|
|
158
|
+
v as default
|
|
165
159
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useRef as u, useEffect as n } from "react";
|
|
2
|
+
const s = (e) => {
|
|
3
|
+
const t = u(e), r = u(!1);
|
|
4
|
+
n(() => {
|
|
5
|
+
t.current = e;
|
|
6
|
+
}, [e]), n(() => (r.current = !1, () => {
|
|
7
|
+
r.current = !0, queueMicrotask(() => {
|
|
8
|
+
r.current && t.current();
|
|
9
|
+
});
|
|
10
|
+
}), []);
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
s as default
|
|
14
|
+
};
|
package/package.json
CHANGED