@fvc/hooks 1.1.1 → 1.1.2-next-ec65dfb844e6183b3d7f417eee613cfe5ecfd997

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 (2) hide show
  1. package/README.md +216 -0
  2. package/package.json +14 -2
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # @fvc/hooks
2
+
3
+ `@fvc/hooks` provides shared React utility hooks for FE-VIS applications. Exposes two low-level primitives that recur across the component library: stable ID resolution for accessibility wiring, and an imperative re-render trigger for state that lives outside React.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @fvc/hooks
9
+ ```
10
+
11
+ ## Peer Dependencies
12
+
13
+ ```bash
14
+ bun add react antd
15
+ ```
16
+
17
+ ## Import
18
+
19
+ ```ts
20
+ import { useId, useForceUpdate } from '@fvc/hooks';
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```tsx
26
+ import { useId } from '@fvc/hooks';
27
+
28
+ export function TextField({ id, label }: { id?: string; label: string }) {
29
+ const fieldId = useId(id);
30
+
31
+ return (
32
+ <>
33
+ <label htmlFor={fieldId}>{label}</label>
34
+ <input id={fieldId} type="text" />
35
+ </>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ## Available Hooks
41
+
42
+ | Hook | Returns | Purpose |
43
+ | --- | --- | --- |
44
+ | [`useId`](#useid) | `string` | Stable unique ID — caller-supplied or auto-generated |
45
+ | [`useForceUpdate`](#useforceupdate) | `() => void` | Unconditional re-render trigger for external state |
46
+
47
+ ## useId
48
+
49
+ A reusable component that renders a `<label>` and `<input>` pair needs an `id` that is stable across re-renders, unique per instance, and overridable by the caller. `useId` handles exactly that: it returns `id` unchanged when provided, and generates a `fvc-{9 base36 chars}` fallback on mount that never changes.
50
+
51
+ > Distinct from React's built-in `useId` — this hook's primary value is the caller-override pattern. React's `useId` is SSR-safe but cannot be overridden by a prop.
52
+
53
+ ### Parameters
54
+
55
+ | Parameter | Type | Default | Description |
56
+ | --- | --- | --- | --- |
57
+ | `id` | `string \| undefined` | `undefined` | When provided, returned as-is — skips generation entirely. |
58
+ | `generateId` | `() => string` | `randomId` | ID factory, called once on mount. Provide this for a custom naming scheme. |
59
+
60
+ ### Common Usage
61
+
62
+ #### Auto-generated ID
63
+
64
+ ```tsx
65
+ function FormField({ label }: { label: string }) {
66
+ const fieldId = useId();
67
+
68
+ return (
69
+ <>
70
+ <label htmlFor={fieldId}>{label}</label>
71
+ <input id={fieldId} />
72
+ </>
73
+ );
74
+ }
75
+ ```
76
+
77
+ #### Caller-supplied ID
78
+
79
+ ```tsx
80
+ // Explicit id — required when a parent anchors aria-describedby or a test
81
+ // selector to a predictable value
82
+ <FormField id="signup-email" label="Email" />
83
+ ```
84
+
85
+ #### Custom ID factory
86
+
87
+ ```tsx
88
+ const prefixed = (prefix: string) => () =>
89
+ `${prefix}-${Math.random().toString(36).slice(2, 9)}`;
90
+
91
+ const fieldId = useId(undefined, prefixed('form'));
92
+ ```
93
+
94
+ ## useForceUpdate
95
+
96
+ Returns a stable dispatch function that triggers an unconditional re-render of the host component. Backed by `useReducer`, the returned reference never changes across renders — it is safe in `useEffect` dependency arrays and cleanup functions.
97
+
98
+ Use this only when state lives outside React's model — a `useRef` value, an external store subscription, or a mutable object from a third-party library — and the view needs to reflect a change React did not observe.
99
+
100
+ ### Parameters
101
+
102
+ `useForceUpdate` takes no parameters.
103
+
104
+ | Returns | Type | Description |
105
+ | --- | --- | --- |
106
+ | `forceUpdate` | `() => void` | Calling it triggers an unconditional re-render. |
107
+
108
+ ### Common Usage
109
+
110
+ #### Sync with an external interval
111
+
112
+ ```tsx
113
+ function LiveClock() {
114
+ const forceUpdate = useForceUpdate();
115
+
116
+ useEffect(() => {
117
+ const timer = setInterval(forceUpdate, 1000);
118
+ return () => clearInterval(timer);
119
+ }, [forceUpdate]);
120
+
121
+ return <time>{new Date().toLocaleTimeString()}</time>;
122
+ }
123
+ ```
124
+
125
+ #### Sync with a mutable ref
126
+
127
+ ```tsx
128
+ function MutableCounter() {
129
+ const forceUpdate = useForceUpdate();
130
+ const count = useRef(0);
131
+
132
+ return (
133
+ <button onClick={() => { count.current++; forceUpdate(); }}>
134
+ Count: {count.current}
135
+ </button>
136
+ );
137
+ }
138
+ ```
139
+
140
+ ## Consumer Example
141
+
142
+ ```tsx
143
+ import { useId, useForceUpdate } from '@fvc/hooks';
144
+ import { useRef, useEffect } from 'react';
145
+
146
+ interface ProgressBarProps {
147
+ id?: string;
148
+ getProgress: () => number;
149
+ label: string;
150
+ }
151
+
152
+ export function ProgressBar({ id, getProgress, label }: ProgressBarProps) {
153
+ const barId = useId(id);
154
+ const forceUpdate = useForceUpdate();
155
+
156
+ useEffect(() => {
157
+ const timer = setInterval(forceUpdate, 500);
158
+ return () => clearInterval(timer);
159
+ }, [forceUpdate]);
160
+
161
+ return (
162
+ <div>
163
+ <label htmlFor={barId}>{label}</label>
164
+ <progress id={barId} value={getProgress()} max={100} />
165
+ </div>
166
+ );
167
+ }
168
+ ```
169
+
170
+ ## Testing
171
+
172
+ Use `renderHook` from `@testing-library/react` to test hooks in isolation.
173
+
174
+ ```tsx
175
+ import { renderHook, act } from '@testing-library/react';
176
+ import { useId, useForceUpdate } from '@fvc/hooks';
177
+
178
+ // useId
179
+ it('returns the provided id', () => {
180
+ const { result } = renderHook(() => useId('my-id'));
181
+ expect(result.current).toBe('my-id');
182
+ });
183
+
184
+ it('generates a stable fvc-* id when no id is provided', () => {
185
+ const { result, rerender } = renderHook(() => useId());
186
+ const initial = result.current;
187
+ rerender();
188
+ expect(result.current).toBe(initial);
189
+ expect(result.current).toMatch(/^fvc-/);
190
+ });
191
+
192
+ // useForceUpdate
193
+ it('triggers a re-render when called', () => {
194
+ let renderCount = 0;
195
+ const { result } = renderHook(() => {
196
+ renderCount++;
197
+ return useForceUpdate();
198
+ });
199
+ act(() => result.current());
200
+ expect(renderCount).toBe(2);
201
+ });
202
+ ```
203
+
204
+ ## SSR Notes
205
+
206
+ Neither hook accesses `window`, `document`, or any browser API — both are safe in server-side environments.
207
+
208
+ `useId` generates its fallback ID with `Math.random()`. In SSR contexts, pass an explicit `id` prop to prevent hydration mismatches between server and client renders.
209
+
210
+ ## Development
211
+
212
+ ```bash
213
+ bun run lint
214
+ bun run type-check
215
+ bun run test
216
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fvc/hooks",
3
- "version": "1.1.1",
3
+ "version": "1.1.2-next-ec65dfb844e6183b3d7f417eee613cfe5ecfd997",
4
4
  "main": "./dist/lib/index.js",
5
5
  "types": "./dist/lib/hooks/src/index.d.ts",
6
6
  "files": [
@@ -20,5 +20,17 @@
20
20
  "peerDependencies": {
21
21
  "react": "^18.0.0",
22
22
  "antd": "^5.0.0"
23
- }
23
+ },
24
+ "keywords": [
25
+ "react",
26
+ "react-component",
27
+ "fvc",
28
+ "fe-vis-core",
29
+ "hooks",
30
+ "use-id",
31
+ "use-force-update",
32
+ "utilities",
33
+ "design-system",
34
+ "antd"
35
+ ]
24
36
  }