rfhook 1.1.2 → 1.2.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.
@@ -0,0 +1 @@
1
+ npx --no -- commitlint --edit
@@ -0,0 +1,3 @@
1
+ dist
2
+ node_modules
3
+ *.log
package/.prettierrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "arrowParens": "always",
3
+ "endOfLine": "auto",
4
+ "printWidth": 80,
5
+ "semi": false,
6
+ "singleAttributePerLine": true,
7
+ "singleQuote": true,
8
+ "tabWidth": 2,
9
+ "trailingComma": "all"
10
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
+
5
+ ## 1.2.0 (2026-01-21)
6
+
7
+
8
+ ### Features
9
+
10
+ * enhance useForm hook with improved type safety and additional utility functions ([73bee0e](https://github.com/evandroishikawa/rfhook/commit/73bee0ee58522deb2c21d8a25ada531c5411e339))
11
+ * initial commit for form-hook ([67f2812](https://github.com/evandroishikawa/rfhook/commit/67f28128b51236ee24727cb1378f679ad646765e))
12
+ * update package name to "rfhook" and add cSpell configuration ([7035768](https://github.com/evandroishikawa/rfhook/commit/7035768698e046c01248bcb93fe2f2e9909ec51f))
13
+ * update README to clarify automatic prevention of default form submission ([4cf0cb6](https://github.com/evandroishikawa/rfhook/commit/4cf0cb657421eb65891d8108b6fab2a27f943ee5))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * bump version to 1.0.1 in package.json ([537382c](https://github.com/evandroishikawa/rfhook/commit/537382c4a080ec9308da4b733e31cfbcc3d37d57))
19
+ * update package name from "@eji/form-hook" to "rfhook" in README.md ([d65177a](https://github.com/evandroishikawa/rfhook/commit/d65177a3cb5fecbb4509414972f786b21fbf9b9e))
package/README.md CHANGED
@@ -9,6 +9,8 @@ A lightweight React hook for handling form submissions with advanced form data p
9
9
  - :wrench: **Nested Objects** - Parse nested form data with dot notation (`user.name`)
10
10
  - :clipboard: **Array Support** - Handle arrays with indexed notation (`items[0]`)
11
11
  - :floppy_disk: **Automatic Prevention** - Prevents default form submission behavior by default
12
+ - :seedling: **Form Initialization** - Pre-populate forms with initial data or load data dynamically
13
+ - :arrows_counterclockwise: **Smart Reset** - Reset to initial state, custom data, or clear completely
12
14
  - :money_with_wings: **Lightweight** - Zero dependencies (except React)
13
15
  - :memo: **Framework Agnostic** - Works with any form structure
14
16
 
@@ -28,6 +30,8 @@ yarn add rfhook
28
30
 
29
31
  ## Quick Start
30
32
 
33
+ ### Basic Usage
34
+
31
35
  ```tsx
32
36
  import React from 'react';
33
37
  import { useForm } from 'rfhook';
@@ -52,7 +56,40 @@ function LoginForm() {
52
56
  </form>
53
57
  );
54
58
  }
55
- <button type="submit">Login</button>
59
+ ```
60
+
61
+ ### With Initial Data
62
+
63
+ ```tsx
64
+ import React, { useEffect } from 'react';
65
+ import { useForm } from 'rfhook';
66
+
67
+ function EditUserForm({ userId }) {
68
+ const form = useForm<FormData>({
69
+ submit: (data) => updateUser(userId, data),
70
+ initialData: { name: '', email: '', phone: '' }
71
+ });
72
+
73
+ // Load user data and initialize form
74
+ useEffect(() => {
75
+ const loadUser = async () => {
76
+ if (userId) {
77
+ const userData = await fetchUser(userId);
78
+ form.initialize(userData);
79
+ } else {
80
+ form.initialize(); // Use initialData
81
+ }
82
+ };
83
+
84
+ loadUser();
85
+ }, [userId]);
86
+
87
+ return (
88
+ <form ref={form.ref} onSubmit={form.onSubmit}>
89
+ <input name="name" placeholder="Name" />
90
+ <input name="email" type="email" placeholder="Email" />
91
+ <input name="phone" placeholder="Phone" />
92
+ <button type="submit">Save</button>
56
93
  </form>
57
94
  );
58
95
  }
@@ -155,27 +192,124 @@ function TodoForm() {
155
192
  #### Parameters
156
193
 
157
194
  - `options.submit: (data: T) => void` - Callback function called when form is submitted with parsed form data
195
+ - `options.initialData?: T` - Optional initial data to populate the form fields
158
196
 
159
197
  #### Returns
160
198
 
161
199
  - `ref: React.RefObject<HTMLFormElement>` - React ref to attach to your form element
162
200
  - `onSubmit: (event: React.FormEvent<HTMLFormElement>) => void` - Form submission handler that prevents default behavior and calls submit with parsed data
163
- - `reset: () => void` - Resets the form to its initial state
201
+ - `reset: (data?: T, shouldUseInitialData?: boolean) => void` - Resets the form. If `data` is provided, sets form to that data. If `shouldUseInitialData` is true (default), falls back to initial data when no data is provided
164
202
  - `getFormData: () => T | null` - Gets current form data without triggering submission (returns null if form ref is not available)
165
203
  - `setValue: (name: string, value: string) => void` - Sets the value of a specific form field by name
204
+ - `initialize: (data?: T) => void` - Initializes the form with provided data or falls back to initial data from options
205
+
206
+ ### Form Initialization Examples
207
+
208
+ #### Static Initial Data
209
+
210
+ ```tsx
211
+ const form = useForm<UserData>({
212
+ submit: (data) => saveUser(data),
213
+ initialData: {
214
+ name: 'John Doe',
215
+ email: 'john@example.com',
216
+ role: 'user'
217
+ }
218
+ });
219
+
220
+ // Initialize with predefined data on component mount
221
+ useEffect(() => {
222
+ form.initialize(); // Uses initialData
223
+ }, []);
224
+ ```
225
+
226
+ #### Dynamic Data Loading
227
+
228
+ ```tsx
229
+ function EditProfile({ userId }) {
230
+ const form = useForm<ProfileData>({
231
+ submit: (data) => updateProfile(userId, data)
232
+ });
233
+
234
+ useEffect(() => {
235
+ const loadProfile = async () => {
236
+ try {
237
+ const profile = await fetchProfile(userId);
238
+ form.initialize(profile);
239
+ } catch (error) {
240
+ console.error('Failed to load profile:', error);
241
+ // Initialize with empty data or defaults
242
+ form.initialize({ name: '', email: '', bio: '' });
243
+ }
244
+ };
245
+
246
+ if (userId) loadProfile();
247
+ }, [userId]);
248
+
249
+ return (
250
+ <form ref={form.ref} onSubmit={form.onSubmit}>
251
+ <input name="name" placeholder="Full Name" />
252
+ <input name="email" type="email" placeholder="Email" />
253
+ <textarea name="bio" placeholder="Bio" />
254
+ <button type="submit">Save Profile</button>
255
+ </form>
256
+ );
257
+ }
258
+ ```
259
+
260
+ #### Conditional Reset Behavior
261
+
262
+ ```tsx
263
+ function FormWithMultipleResets() {
264
+ const form = useForm<FormData>({
265
+ submit: (data) => console.log(data),
266
+ initialData: { name: 'Default Name', email: '' }
267
+ });
166
268
 
167
- ### Additional Usage Examples
269
+ return (
270
+ <>
271
+ <form ref={form.ref} onSubmit={form.onSubmit}>
272
+ <input name="name" placeholder="Name" />
273
+ <input name="email" placeholder="Email" />
274
+ <button type="submit">Submit</button>
275
+ </form>
276
+
277
+ <div>
278
+ {/* Reset to initial data */}
279
+ <button onClick={() => form.reset()}>Reset to Defaults</button>
280
+
281
+ {/* Reset to custom data */}
282
+ <button onClick={() => form.reset({ name: 'Custom Name', email: 'custom@example.com' })}>
283
+ Reset to Custom
284
+ </button>
285
+
286
+ {/* Clear form completely */}
287
+ <button onClick={() => form.reset({}, false)}>Clear Form</button>
288
+ </div>
289
+ </>
290
+ );
291
+ }
292
+ ```
168
293
 
169
294
  #### Using Form Utilities
170
295
 
171
296
  ```tsx
172
297
  function MyForm() {
173
298
  const form = useForm<FormData>({
174
- submit: (data) => console.log('Submitted:', data)
299
+ submit: (data) => console.log('Submitted:', data),
300
+ initialData: { name: '', email: '', age: '' }
175
301
  });
176
302
 
177
303
  const handleReset = () => {
178
- form.reset(); // Reset form to initial state
304
+ form.reset(); // Reset to initial data
305
+ };
306
+
307
+ const handleClearForm = () => {
308
+ form.reset({}, false); // Clear form completely
309
+ };
310
+
311
+ const handleResetToDefaults = () => {
312
+ form.reset({ name: 'Default User', email: '', age: '25' }); // Reset to specific data
179
313
  };
180
314
 
181
315
  const handlePreview = () => {
@@ -190,18 +324,27 @@ function MyForm() {
190
324
  form.setValue('name', 'John Doe');
191
325
  };
192
326
 
327
+ const handleLoadFromAPI = async () => {
328
+ const userData = await fetchUserData();
329
+ form.initialize(userData);
330
+ };
331
+
193
332
  return (
194
333
  <>
195
334
  <form ref={form.ref} onSubmit={form.onSubmit}>
196
335
  <input name="name" placeholder="Name" />
197
336
  <input name="email" type="email" placeholder="Email" />
337
+ <input name="age" type="number" placeholder="Age" />
198
338
  <button type="submit">Submit</button>
199
339
  </form>
200
340
 
201
341
  <div>
202
- <button onClick={handleReset}>Reset Form</button>
342
+ <button onClick={handleReset}>Reset to Initial</button>
343
+ <button onClick={handleClearForm}>Clear Form</button>
344
+ <button onClick={handleResetToDefaults}>Reset to Defaults</button>
203
345
  <button onClick={handlePreview}>Preview Data</button>
204
346
  <button onClick={handlePrefill}>Prefill Form</button>
347
+ <button onClick={handleLoadFromAPI}>Load from API</button>
205
348
  </div>
206
349
  </>
207
350
  );
@@ -318,5 +461,23 @@ pnpm install
318
461
  # Build the package
319
462
  pnpm run build
320
463
 
464
+ # Code quality
465
+ pnpm run lint # Check for linting issues
466
+ pnpm run lint:fix # Auto-fix linting issues
467
+ pnpm run format # Format code with Prettier
468
+ pnpm run format:check # Check code formatting
469
+
321
470
  # The built files will be in the `dist/` directory
322
471
  ```
472
+
473
+ ### Code Style
474
+
475
+ This project uses:
476
+
477
+ - **ESLint** for code linting with TypeScript and React support
478
+ - **Prettier** for code formatting with these rules:
479
+ - Single quotes
480
+ - No semicolons
481
+ - Trailing commas
482
+ - 2-space indentation
483
+ - 80 character line width
@@ -0,0 +1,20 @@
1
+ const commitTypes = [
2
+ 'chore',
3
+ 'fix',
4
+ 'feat',
5
+ 'docs',
6
+ 'style',
7
+ 'refactor',
8
+ 'test',
9
+ 'ci',
10
+ 'build',
11
+ 'revert',
12
+ 'wip',
13
+ ]
14
+
15
+ export default {
16
+ extends: ['@commitlint/config-conventional'],
17
+ rules: {
18
+ 'type-enum': [2, 'always', commitTypes],
19
+ },
20
+ }
package/dist/index.cjs CHANGED
@@ -40,7 +40,9 @@ function parseFormData(formData) {
40
40
  continue;
41
41
  }
42
42
  // Initialize nested object and move to it
43
- if (!current[key] || typeof current[key] !== 'object' || Array.isArray(current[key])) {
43
+ if (!current[key] ||
44
+ typeof current[key] !== 'object' ||
45
+ Array.isArray(current[key])) {
44
46
  current[key] = {};
45
47
  }
46
48
  current = current[key];
@@ -50,13 +52,14 @@ function parseFormData(formData) {
50
52
  }
51
53
 
52
54
  /**
53
- * A React hook for handling form state and submission
55
+ * A React hook for handling form state and submission with initialization support
54
56
  *
55
57
  * @template T - The expected shape of the parsed form data
56
58
  * @param options - Configuration options for the form
57
59
  * @returns Object containing form ref, handlers, and utility functions
58
60
  *
59
61
  * @example
62
+ * Basic usage:
60
63
  * ```typescript
61
64
  * interface LoginData {
62
65
  * email: string;
@@ -75,9 +78,43 @@ function parseFormData(formData) {
75
78
  * </form>
76
79
  * );
77
80
  * ```
81
+ *
82
+ * @example
83
+ * With initial data:
84
+ * ```typescript
85
+ * const form = useForm<LoginData>({
86
+ * submit: (data) => console.log(data),
87
+ * initialData: { email: 'user@example.com', password: '' }
88
+ * });
89
+ *
90
+ * // Initialize form with initial data on mount
91
+ * useEffect(() => {
92
+ * form.initialize();
93
+ * }, []);
94
+ * ```
95
+ *
96
+ * @example
97
+ * Dynamic initialization:
98
+ * ```typescript
99
+ * const form = useForm<UserData>({
100
+ * submit: (data) => saveUser(data)
101
+ * });
102
+ *
103
+ * // Load and set user data from API
104
+ * const loadUser = async (userId: string) => {
105
+ * const userData = await fetchUser(userId);
106
+ * form.initialize(userData);
107
+ * };
108
+ *
109
+ * // Reset to specific data
110
+ * const resetToDefaults = () => {
111
+ * form.reset({ name: '', email: '', age: '' });
112
+ * };
113
+ * ```
78
114
  */
79
- function useForm({ submit }) {
115
+ function useForm({ submit, initialData, }) {
80
116
  const ref = react.useRef(null);
117
+ const initialDataRef = react.useRef(initialData);
81
118
  const onSubmit = (event) => {
82
119
  event.preventDefault();
83
120
  if (!ref.current)
@@ -86,10 +123,16 @@ function useForm({ submit }) {
86
123
  const data = parseFormData(formData);
87
124
  submit(data);
88
125
  };
89
- const reset = () => {
126
+ const reset = (data, shouldUseInitialData = true) => {
90
127
  if (!ref.current)
91
128
  return;
92
129
  ref.current.reset();
130
+ if (data) {
131
+ return initialize(data);
132
+ }
133
+ if (initialDataRef.current && shouldUseInitialData) {
134
+ initialize(initialDataRef.current);
135
+ }
93
136
  };
94
137
  const getFormData = () => {
95
138
  if (!ref.current)
@@ -106,12 +149,22 @@ function useForm({ submit }) {
106
149
  element.value = value;
107
150
  }
108
151
  };
152
+ const initialize = (data) => {
153
+ const dataToUse = data || initialDataRef.current;
154
+ if (!ref.current || !dataToUse)
155
+ return;
156
+ Object.entries(dataToUse).forEach(([key, value]) => {
157
+ if (value)
158
+ setValue(key, String(value));
159
+ });
160
+ };
109
161
  return {
110
162
  ref,
163
+ getFormData,
164
+ initialize,
111
165
  onSubmit,
112
166
  reset,
113
- getFormData,
114
- setValue
167
+ setValue,
115
168
  };
116
169
  }
117
170
 
package/dist/index.js CHANGED
@@ -38,7 +38,9 @@ function parseFormData(formData) {
38
38
  continue;
39
39
  }
40
40
  // Initialize nested object and move to it
41
- if (!current[key] || typeof current[key] !== 'object' || Array.isArray(current[key])) {
41
+ if (!current[key] ||
42
+ typeof current[key] !== 'object' ||
43
+ Array.isArray(current[key])) {
42
44
  current[key] = {};
43
45
  }
44
46
  current = current[key];
@@ -48,13 +50,14 @@ function parseFormData(formData) {
48
50
  }
49
51
 
50
52
  /**
51
- * A React hook for handling form state and submission
53
+ * A React hook for handling form state and submission with initialization support
52
54
  *
53
55
  * @template T - The expected shape of the parsed form data
54
56
  * @param options - Configuration options for the form
55
57
  * @returns Object containing form ref, handlers, and utility functions
56
58
  *
57
59
  * @example
60
+ * Basic usage:
58
61
  * ```typescript
59
62
  * interface LoginData {
60
63
  * email: string;
@@ -73,9 +76,43 @@ function parseFormData(formData) {
73
76
  * </form>
74
77
  * );
75
78
  * ```
79
+ *
80
+ * @example
81
+ * With initial data:
82
+ * ```typescript
83
+ * const form = useForm<LoginData>({
84
+ * submit: (data) => console.log(data),
85
+ * initialData: { email: 'user@example.com', password: '' }
86
+ * });
87
+ *
88
+ * // Initialize form with initial data on mount
89
+ * useEffect(() => {
90
+ * form.initialize();
91
+ * }, []);
92
+ * ```
93
+ *
94
+ * @example
95
+ * Dynamic initialization:
96
+ * ```typescript
97
+ * const form = useForm<UserData>({
98
+ * submit: (data) => saveUser(data)
99
+ * });
100
+ *
101
+ * // Load and set user data from API
102
+ * const loadUser = async (userId: string) => {
103
+ * const userData = await fetchUser(userId);
104
+ * form.initialize(userData);
105
+ * };
106
+ *
107
+ * // Reset to specific data
108
+ * const resetToDefaults = () => {
109
+ * form.reset({ name: '', email: '', age: '' });
110
+ * };
111
+ * ```
76
112
  */
77
- function useForm({ submit }) {
113
+ function useForm({ submit, initialData, }) {
78
114
  const ref = useRef(null);
115
+ const initialDataRef = useRef(initialData);
79
116
  const onSubmit = (event) => {
80
117
  event.preventDefault();
81
118
  if (!ref.current)
@@ -84,10 +121,16 @@ function useForm({ submit }) {
84
121
  const data = parseFormData(formData);
85
122
  submit(data);
86
123
  };
87
- const reset = () => {
124
+ const reset = (data, shouldUseInitialData = true) => {
88
125
  if (!ref.current)
89
126
  return;
90
127
  ref.current.reset();
128
+ if (data) {
129
+ return initialize(data);
130
+ }
131
+ if (initialDataRef.current && shouldUseInitialData) {
132
+ initialize(initialDataRef.current);
133
+ }
91
134
  };
92
135
  const getFormData = () => {
93
136
  if (!ref.current)
@@ -104,12 +147,22 @@ function useForm({ submit }) {
104
147
  element.value = value;
105
148
  }
106
149
  };
150
+ const initialize = (data) => {
151
+ const dataToUse = data || initialDataRef.current;
152
+ if (!ref.current || !dataToUse)
153
+ return;
154
+ Object.entries(dataToUse).forEach(([key, value]) => {
155
+ if (value)
156
+ setValue(key, String(value));
157
+ });
158
+ };
107
159
  return {
108
160
  ref,
161
+ getFormData,
162
+ initialize,
109
163
  onSubmit,
110
164
  reset,
111
- getFormData,
112
- setValue
165
+ setValue,
113
166
  };
114
167
  }
115
168
 
@@ -4,6 +4,8 @@
4
4
  interface UseFormOptions<T> {
5
5
  /** Callback function called when form is submitted with parsed form data */
6
6
  submit: (data: T) => void;
7
+ /** Initial data to populate the form fields */
8
+ initialData?: T;
7
9
  }
8
10
  /**
9
11
  * Return type of the useForm hook
@@ -13,21 +15,24 @@ interface UseFormReturn<T> {
13
15
  ref: React.RefObject<HTMLFormElement | null>;
14
16
  /** Form submission handler - prevents default and calls submit with parsed data */
15
17
  onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
16
- /** Resets the form to its initial state */
17
- reset: () => void;
18
+ /** Resets the form and optionally sets it to provided data or initial data */
19
+ reset: (data?: T, shouldUseInitialData?: boolean) => void;
18
20
  /** Gets current form data without triggering submission */
19
21
  getFormData: () => T | null;
20
22
  /** Sets the value of a specific form field by name */
21
23
  setValue: (name: string, value: string) => void;
24
+ /** Initializes the form with provided data or initial data from options */
25
+ initialize: (data?: T) => void;
22
26
  }
23
27
  /**
24
- * A React hook for handling form state and submission
28
+ * A React hook for handling form state and submission with initialization support
25
29
  *
26
30
  * @template T - The expected shape of the parsed form data
27
31
  * @param options - Configuration options for the form
28
32
  * @returns Object containing form ref, handlers, and utility functions
29
33
  *
30
34
  * @example
35
+ * Basic usage:
31
36
  * ```typescript
32
37
  * interface LoginData {
33
38
  * email: string;
@@ -46,6 +51,39 @@ interface UseFormReturn<T> {
46
51
  * </form>
47
52
  * );
48
53
  * ```
54
+ *
55
+ * @example
56
+ * With initial data:
57
+ * ```typescript
58
+ * const form = useForm<LoginData>({
59
+ * submit: (data) => console.log(data),
60
+ * initialData: { email: 'user@example.com', password: '' }
61
+ * });
62
+ *
63
+ * // Initialize form with initial data on mount
64
+ * useEffect(() => {
65
+ * form.initialize();
66
+ * }, []);
67
+ * ```
68
+ *
69
+ * @example
70
+ * Dynamic initialization:
71
+ * ```typescript
72
+ * const form = useForm<UserData>({
73
+ * submit: (data) => saveUser(data)
74
+ * });
75
+ *
76
+ * // Load and set user data from API
77
+ * const loadUser = async (userId: string) => {
78
+ * const userData = await fetchUser(userId);
79
+ * form.initialize(userData);
80
+ * };
81
+ *
82
+ * // Reset to specific data
83
+ * const resetToDefaults = () => {
84
+ * form.reset({ name: '', email: '', age: '' });
85
+ * };
86
+ * ```
49
87
  */
50
- export declare function useForm<T = Record<string, unknown>>({ submit }: UseFormOptions<T>): UseFormReturn<T>;
88
+ export declare function useForm<T = Record<string, unknown>>({ submit, initialData, }: UseFormOptions<T>): UseFormReturn<T>;
51
89
  export {};
@@ -0,0 +1,50 @@
1
+ import js from '@eslint/js'
2
+ import tseslint from '@typescript-eslint/eslint-plugin'
3
+ import tsparser from '@typescript-eslint/parser'
4
+ import react from 'eslint-plugin-react'
5
+ import reactHooks from 'eslint-plugin-react-hooks'
6
+ import prettier from 'eslint-plugin-prettier'
7
+
8
+ export default [
9
+ js.configs.recommended,
10
+ {
11
+ files: ['**/*.{ts,tsx,js}'],
12
+ languageOptions: {
13
+ parser: tsparser,
14
+ parserOptions: {
15
+ ecmaVersion: 'latest',
16
+ sourceType: 'module',
17
+ ecmaFeatures: {
18
+ jsx: true,
19
+ },
20
+ },
21
+ },
22
+ plugins: {
23
+ '@typescript-eslint': tseslint,
24
+ prettier,
25
+ react: react,
26
+ 'react-hooks': reactHooks,
27
+ },
28
+ rules: {
29
+ ...tseslint.configs.recommended.rules,
30
+ ...react.configs.recommended.rules,
31
+ ...reactHooks.configs.recommended.rules,
32
+ 'react/react-in-jsx-scope': 'off',
33
+ 'react/prop-types': 'off',
34
+ '@typescript-eslint/no-unused-vars': [
35
+ 'error',
36
+ { argsIgnorePattern: '^_' },
37
+ ],
38
+ 'no-undef': 'off',
39
+ 'prettier/prettier': 'error',
40
+ },
41
+ settings: {
42
+ react: {
43
+ version: 'detect',
44
+ },
45
+ },
46
+ },
47
+ {
48
+ ignores: ['dist/**', 'node_modules/**', '*.config.js'],
49
+ },
50
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rfhook",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -18,15 +18,32 @@
18
18
  "react-dom": "^19.2.3"
19
19
  },
20
20
  "devDependencies": {
21
+ "@commitlint/cli": "^20.3.1",
22
+ "@commitlint/config-conventional": "^20.3.1",
23
+ "@eslint/js": "^9.39.2",
21
24
  "@rollup/plugin-node-resolve": "^16.0.3",
22
25
  "@rollup/plugin-typescript": "^12.3.0",
23
- "@types/react": "^19.2.8",
26
+ "@types/react": "^19.2.9",
24
27
  "@types/react-dom": "^19.2.3",
28
+ "@typescript-eslint/eslint-plugin": "^8.53.1",
29
+ "@typescript-eslint/parser": "^8.53.1",
30
+ "eslint": "^9.39.2",
31
+ "eslint-plugin-prettier": "^5.5.5",
32
+ "eslint-plugin-react": "^7.37.5",
33
+ "eslint-plugin-react-hooks": "^5.2.0",
34
+ "husky": "^9.1.7",
35
+ "prettier": "^3.8.0",
25
36
  "rollup": "^4.55.2",
37
+ "standard-version": "^9.5.0",
26
38
  "tslib": "^2.8.1",
27
39
  "typescript": "^5.9.3"
28
40
  },
29
41
  "scripts": {
30
- "build": "rollup -c"
42
+ "build": "rollup -c",
43
+ "lint": "eslint src --ext .ts,.tsx",
44
+ "lint:fix": "eslint src --ext .ts,.tsx --fix",
45
+ "format": "prettier --write \"src/**/*.{ts,tsx}\"",
46
+ "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
47
+ "release": "standard-version"
31
48
  }
32
49
  }
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { useForm } from './useForm.hook';
1
+ export { useForm } from './useForm.hook'
@@ -1,44 +1,49 @@
1
- import { useRef } from 'react';
1
+ import { useRef } from 'react'
2
2
 
3
- import { parseFormData } from './utils/parseFormData';
3
+ import { parseFormData } from './utils/parseFormData'
4
4
 
5
5
  /**
6
6
  * Configuration options for the useForm hook
7
7
  */
8
8
  interface UseFormOptions<T> {
9
9
  /** Callback function called when form is submitted with parsed form data */
10
- submit: (data: T) => void;
10
+ submit: (data: T) => void
11
+ /** Initial data to populate the form fields */
12
+ initialData?: T
11
13
  }
12
14
 
13
15
  /**
14
16
  * Form element types that can have their values set programmatically
15
17
  */
16
- type FormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
18
+ type FormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
17
19
 
18
20
  /**
19
21
  * Return type of the useForm hook
20
22
  */
21
23
  interface UseFormReturn<T> {
22
24
  /** React ref to be attached to the form element */
23
- ref: React.RefObject<HTMLFormElement | null>;
25
+ ref: React.RefObject<HTMLFormElement | null>
24
26
  /** Form submission handler - prevents default and calls submit with parsed data */
25
- onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
26
- /** Resets the form to its initial state */
27
- reset: () => void;
27
+ onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
28
+ /** Resets the form and optionally sets it to provided data or initial data */
29
+ reset: (data?: T, shouldUseInitialData?: boolean) => void
28
30
  /** Gets current form data without triggering submission */
29
- getFormData: () => T | null;
31
+ getFormData: () => T | null
30
32
  /** Sets the value of a specific form field by name */
31
- setValue: (name: string, value: string) => void;
33
+ setValue: (name: string, value: string) => void
34
+ /** Initializes the form with provided data or initial data from options */
35
+ initialize: (data?: T) => void
32
36
  }
33
37
 
34
38
  /**
35
- * A React hook for handling form state and submission
39
+ * A React hook for handling form state and submission with initialization support
36
40
  *
37
41
  * @template T - The expected shape of the parsed form data
38
42
  * @param options - Configuration options for the form
39
43
  * @returns Object containing form ref, handlers, and utility functions
40
44
  *
41
45
  * @example
46
+ * Basic usage:
42
47
  * ```typescript
43
48
  * interface LoginData {
44
49
  * email: string;
@@ -57,55 +62,111 @@ interface UseFormReturn<T> {
57
62
  * </form>
58
63
  * );
59
64
  * ```
65
+ *
66
+ * @example
67
+ * With initial data:
68
+ * ```typescript
69
+ * const form = useForm<LoginData>({
70
+ * submit: (data) => console.log(data),
71
+ * initialData: { email: 'user@example.com', password: '' }
72
+ * });
73
+ *
74
+ * // Initialize form with initial data on mount
75
+ * useEffect(() => {
76
+ * form.initialize();
77
+ * }, []);
78
+ * ```
79
+ *
80
+ * @example
81
+ * Dynamic initialization:
82
+ * ```typescript
83
+ * const form = useForm<UserData>({
84
+ * submit: (data) => saveUser(data)
85
+ * });
86
+ *
87
+ * // Load and set user data from API
88
+ * const loadUser = async (userId: string) => {
89
+ * const userData = await fetchUser(userId);
90
+ * form.initialize(userData);
91
+ * };
92
+ *
93
+ * // Reset to specific data
94
+ * const resetToDefaults = () => {
95
+ * form.reset({ name: '', email: '', age: '' });
96
+ * };
97
+ * ```
60
98
  */
61
- export function useForm<T = Record<string, unknown>>(
62
- { submit }: UseFormOptions<T>
63
- ): UseFormReturn<T> {
64
- const ref = useRef<HTMLFormElement>(null);
99
+ export function useForm<T = Record<string, unknown>>({
100
+ submit,
101
+ initialData,
102
+ }: UseFormOptions<T>): UseFormReturn<T> {
103
+ const ref = useRef<HTMLFormElement>(null)
104
+ const initialDataRef = useRef<T | undefined>(initialData)
65
105
 
66
106
  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
67
- event.preventDefault();
107
+ event.preventDefault()
68
108
 
69
- if (!ref.current) return;
109
+ if (!ref.current) return
70
110
 
71
- const formData = new FormData(ref.current);
111
+ const formData = new FormData(ref.current)
72
112
 
73
- const data = parseFormData(formData) as T;
113
+ const data = parseFormData(formData) as T
74
114
 
75
- submit(data);
76
- }
115
+ submit(data)
116
+ }
117
+
118
+ const reset = (data?: T, shouldUseInitialData: boolean = true) => {
119
+ if (!ref.current) return
77
120
 
78
- const reset = () => {
79
- if (!ref.current) return;
121
+ ref.current.reset()
80
122
 
81
- ref.current.reset();
82
- };
123
+ if (data) {
124
+ return initialize(data)
125
+ }
126
+
127
+ if (initialDataRef.current && shouldUseInitialData) {
128
+ initialize(initialDataRef.current)
129
+ }
130
+ }
83
131
 
84
132
  const getFormData = (): T | null => {
85
- if (!ref.current) return null;
133
+ if (!ref.current) return null
86
134
 
87
- const formData = new FormData(ref.current);
135
+ const formData = new FormData(ref.current)
88
136
 
89
- const data = parseFormData(formData) as T;
137
+ const data = parseFormData(formData) as T
90
138
 
91
- return data;
92
- };
139
+ return data
140
+ }
93
141
 
94
142
  const setValue = (name: string, value: string) => {
95
- if (!ref.current) return;
143
+ if (!ref.current) return
96
144
 
97
- const element = ref.current.elements.namedItem(name) as FormElement | null;
145
+ const element = ref.current.elements.namedItem(name) as FormElement | null
98
146
 
99
147
  if (element && 'value' in element) {
100
- element.value = value;
148
+ element.value = value
101
149
  }
102
- };
150
+ }
151
+
152
+ const initialize = (data?: T) => {
153
+ const dataToUse = data || initialDataRef.current
154
+
155
+ if (!ref.current || !dataToUse) return
156
+
157
+ Object.entries(dataToUse as Record<string, unknown>).forEach(
158
+ ([key, value]) => {
159
+ if (value) setValue(key, String(value))
160
+ },
161
+ )
162
+ }
103
163
 
104
164
  return {
105
165
  ref,
166
+ getFormData,
167
+ initialize,
106
168
  onSubmit,
107
169
  reset,
108
- getFormData,
109
- setValue
110
- };
111
- }
170
+ setValue,
171
+ }
172
+ }
@@ -1,60 +1,64 @@
1
- const ARRAY_KEY_REGEX = /^(\w+)\[(\d+)\]$/;
1
+ const ARRAY_KEY_REGEX = /^(\w+)\[(\d+)\]$/
2
2
 
3
3
  export function parseFormData(formData: FormData): Record<string, unknown> {
4
- const data = Object.fromEntries(formData.entries());
5
- const result: Record<string, unknown> = {};
4
+ const data = Object.fromEntries(formData.entries())
5
+ const result: Record<string, unknown> = {}
6
6
 
7
7
  for (const [path, value] of Object.entries(data)) {
8
8
  const keys = path.split('.')
9
- let current = result;
9
+ let current = result
10
10
 
11
11
  for (let i = 0; i < keys.length; i++) {
12
- const key = keys[i];
13
- const isLastKey = i === keys.length - 1;
14
- const arrayMatch = key.match(ARRAY_KEY_REGEX);
12
+ const key = keys[i]
13
+ const isLastKey = i === keys.length - 1
14
+ const arrayMatch = key.match(ARRAY_KEY_REGEX)
15
15
 
16
16
  // Handle array notation like items[0]
17
17
  if (arrayMatch) {
18
- const [, arrayKey, indexStr] = arrayMatch;
19
- const index = parseInt(indexStr, 10);
18
+ const [, arrayKey, indexStr] = arrayMatch
19
+ const index = parseInt(indexStr, 10)
20
20
 
21
21
  // Initialize array if it doesn't exist
22
22
  if (!Array.isArray(current[arrayKey])) {
23
- current[arrayKey] = [];
23
+ current[arrayKey] = []
24
24
  }
25
25
 
26
- const array = current[arrayKey] as unknown[];
26
+ const array = current[arrayKey] as unknown[]
27
27
 
28
28
  // Set value and continue if this is the last key
29
29
  if (isLastKey) {
30
- array[index] = value;
31
- continue;
30
+ array[index] = value
31
+ continue
32
32
  }
33
33
 
34
34
  // Initialize nested object and move to it
35
35
  if (!array[index] || typeof array[index] !== 'object') {
36
- array[index] = {};
36
+ array[index] = {}
37
37
  }
38
38
 
39
- current = array[index] as Record<string, unknown>;
39
+ current = array[index] as Record<string, unknown>
40
40
 
41
- continue;
41
+ continue
42
42
  }
43
43
 
44
44
  // Handle regular keys - set value and continue if this is the last key
45
45
  if (isLastKey) {
46
- current[key] = value;
47
- continue;
46
+ current[key] = value
47
+ continue
48
48
  }
49
49
 
50
50
  // Initialize nested object and move to it
51
- if (!current[key] || typeof current[key] !== 'object' || Array.isArray(current[key])) {
52
- current[key] = {};
51
+ if (
52
+ !current[key] ||
53
+ typeof current[key] !== 'object' ||
54
+ Array.isArray(current[key])
55
+ ) {
56
+ current[key] = {}
53
57
  }
54
58
 
55
- current = current[key] as Record<string, unknown>;
59
+ current = current[key] as Record<string, unknown>
56
60
  }
57
61
  }
58
62
 
59
- return result;
63
+ return result
60
64
  }
package/tsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2020",
4
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
5
5
  "module": "ESNext",
6
6
  "moduleResolution": "node",
7
7
  "declaration": true,