@conform-to/react 1.0.5 → 1.1.0-pre.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.
package/README CHANGED
@@ -8,7 +8,7 @@
8
8
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
9
9
 
10
10
 
11
- Version 1.0.4 / License MIT / Copyright (c) 2024 Edmund Hung
11
+ Version 1.1.0-pre.0 / License MIT / Copyright (c) 2024 Edmund Hung
12
12
 
13
13
  A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
14
14
 
package/context.d.ts CHANGED
@@ -54,9 +54,9 @@ export declare function FormStateInput(props: {
54
54
  }): React.ReactElement;
55
55
  export declare function useSubjectRef(initialSubject?: SubscriptionSubject): MutableRefObject<SubscriptionSubject>;
56
56
  export declare function updateSubjectRef(ref: MutableRefObject<SubscriptionSubject>, name: string, subject: keyof SubscriptionSubject, scope: keyof SubscriptionScope): void;
57
- export declare function getMetadata<Schema, FormError, FormSchema extends Record<string, any>>(formId: FormId<FormSchema, FormError>, state: FormState<FormError>, subjectRef: MutableRefObject<SubscriptionSubject>, name?: FieldName<Schema, FormSchema, FormError>): Metadata<Schema, FormSchema, FormError>;
58
- export declare function getFieldMetadata<Schema, FormSchema extends Record<string, any>, FormError>(formId: FormId<FormSchema, FormError>, state: FormState<FormError>, subjectRef: MutableRefObject<SubscriptionSubject>, prefix?: string, key?: string | number): FieldMetadata<Schema, FormSchema, FormError>;
59
- export declare function getFormMetadata<Schema extends Record<string, any>, FormError = string[], FormValue = Schema>(formId: FormId<Schema, FormError>, state: FormState<FormError>, subjectRef: MutableRefObject<SubscriptionSubject>, context: FormContext<Schema, FormError, FormValue>, noValidate: boolean): FormMetadata<Schema, FormError>;
57
+ export declare function getMetadata<Schema, FormError, FormSchema extends Record<string, any>>(context: FormContext<FormSchema, FormError, any>, subjectRef: MutableRefObject<SubscriptionSubject>, stateSnapshot: FormState<FormError>, name?: FieldName<Schema, FormSchema, FormError>): Metadata<Schema, FormSchema, FormError>;
58
+ export declare function getFieldMetadata<Schema, FormSchema extends Record<string, any>, FormError>(context: FormContext<FormSchema, FormError, any>, subjectRef: MutableRefObject<SubscriptionSubject>, stateSnapshot: FormState<FormError>, prefix?: string, key?: string | number): FieldMetadata<Schema, FormSchema, FormError>;
59
+ export declare function getFormMetadata<Schema extends Record<string, any>, FormError = string[], FormValue = Schema>(context: FormContext<Schema, FormError, FormValue>, subjectRef: MutableRefObject<SubscriptionSubject>, stateSnapshot: FormState<FormError>, noValidate: boolean): FormMetadata<Schema, FormError>;
60
60
  export type FormOptions<Schema extends Record<string, any> = any, FormError = string[], FormValue = Schema> = BaseFormOptions<Schema, FormError, FormValue> & {
61
61
  /**
62
62
  * A function to be called before the form is submitted.
package/context.js CHANGED
@@ -66,9 +66,10 @@ function updateSubjectRef(ref, name, subject, scope) {
66
66
  });
67
67
  }
68
68
  }
69
- function getMetadata(formId, state, subjectRef) {
69
+ function getMetadata(context, subjectRef, stateSnapshot) {
70
70
  var name = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
71
- var id = name ? "".concat(formId, "-").concat(name) : formId;
71
+ var id = name ? "".concat(context.formId, "-").concat(name) : context.formId;
72
+ var state = context.getState();
72
73
  return new Proxy({
73
74
  id,
74
75
  name,
@@ -108,7 +109,7 @@ function getMetadata(formId, state, subjectRef) {
108
109
  return () => new Proxy({}, {
109
110
  get(target, key, receiver) {
110
111
  if (typeof key === 'string') {
111
- return getFieldMetadata(formId, state, subjectRef, name, key);
112
+ return getFieldMetadata(context, subjectRef, stateSnapshot, name, key);
112
113
  }
113
114
  return Reflect.get(target, key, receiver);
114
115
  }
@@ -116,34 +117,40 @@ function getMetadata(formId, state, subjectRef) {
116
117
  }
117
118
  }, {
118
119
  get(target, key, receiver) {
119
- switch (key) {
120
- case 'key':
121
- case 'initialValue':
122
- case 'value':
123
- case 'valid':
124
- case 'dirty':
125
- updateSubjectRef(subjectRef, name, key, 'name');
126
- break;
127
- case 'errors':
128
- case 'allErrors':
129
- updateSubjectRef(subjectRef, name, 'error', key === 'errors' ? 'name' : 'prefix');
130
- break;
120
+ // We want to minize re-render by identifying whether the field is used in a callback only
121
+ // but there is no clear way to know if it is accessed during render or not
122
+ // if the stateSnapshot is not the latest, then it must be accessed in a callback
123
+ if (state === stateSnapshot) {
124
+ switch (key) {
125
+ case 'key':
126
+ case 'initialValue':
127
+ case 'value':
128
+ case 'valid':
129
+ case 'dirty':
130
+ updateSubjectRef(subjectRef, name, key, 'name');
131
+ break;
132
+ case 'errors':
133
+ case 'allErrors':
134
+ updateSubjectRef(subjectRef, name, 'error', key === 'errors' ? 'name' : 'prefix');
135
+ break;
136
+ }
131
137
  }
132
138
  return Reflect.get(target, key, receiver);
133
139
  }
134
140
  });
135
141
  }
136
- function getFieldMetadata(formId, state, subjectRef) {
142
+ function getFieldMetadata(context, subjectRef, stateSnapshot) {
137
143
  var prefix = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
138
144
  var key = arguments.length > 4 ? arguments[4] : undefined;
139
145
  var name = typeof key === 'undefined' ? prefix : dom.formatPaths([...dom.getPaths(prefix), key]);
140
- var metadata = getMetadata(formId, state, subjectRef, name);
141
146
  return new Proxy({}, {
142
147
  get(_, key, receiver) {
143
148
  var _state$constraint$nam;
149
+ var metadata = getMetadata(context, subjectRef, stateSnapshot, name);
150
+ var state = context.getState();
144
151
  switch (key) {
145
152
  case 'formId':
146
- return formId;
153
+ return context.formId;
147
154
  case 'required':
148
155
  case 'minLength':
149
156
  case 'maxLength':
@@ -158,11 +165,13 @@ function getFieldMetadata(formId, state, subjectRef) {
158
165
  return () => {
159
166
  var _state$initialValue$n;
160
167
  var initialValue = (_state$initialValue$n = state.initialValue[name]) !== null && _state$initialValue$n !== void 0 ? _state$initialValue$n : [];
161
- updateSubjectRef(subjectRef, name, 'initialValue', 'name');
168
+ if (state === stateSnapshot) {
169
+ updateSubjectRef(subjectRef, name, 'initialValue', 'name');
170
+ }
162
171
  if (!Array.isArray(initialValue)) {
163
172
  throw new Error('The initial value at the given name is not a list');
164
173
  }
165
- return Array(initialValue.length).fill(0).map((_, index) => getFieldMetadata(formId, state, subjectRef, name, index));
174
+ return Array(initialValue.length).fill(0).map((_, index) => getFieldMetadata(context, subjectRef, stateSnapshot, name, index));
166
175
  };
167
176
  }
168
177
  }
@@ -170,10 +179,11 @@ function getFieldMetadata(formId, state, subjectRef) {
170
179
  }
171
180
  });
172
181
  }
173
- function getFormMetadata(formId, state, subjectRef, context, noValidate) {
174
- var metadata = getMetadata(formId, state, subjectRef);
182
+ function getFormMetadata(context, subjectRef, stateSnapshot, noValidate) {
175
183
  return new Proxy({}, {
176
184
  get(_, key, receiver) {
185
+ var metadata = getMetadata(context, subjectRef, stateSnapshot);
186
+ var state = context.getState();
177
187
  switch (key) {
178
188
  case 'context':
179
189
  return {
@@ -207,11 +217,13 @@ function createFormContext(options) {
207
217
  submit(event) {
208
218
  var submitEvent = event.nativeEvent;
209
219
  var result = context.submit(submitEvent);
210
- if (result.submission && result.submission.status !== 'success' && result.submission.error !== null) {
211
- event.preventDefault();
220
+ if (!result.submission || result.submission.status === 'success' || result.submission.error === null) {
221
+ if (!result.formData.has(dom.INTENT)) {
222
+ var _onSubmit;
223
+ (_onSubmit = onSubmit) === null || _onSubmit === void 0 || _onSubmit(event, result);
224
+ }
212
225
  } else {
213
- var _onSubmit;
214
- (_onSubmit = onSubmit) === null || _onSubmit === void 0 || _onSubmit(event, result);
226
+ event.preventDefault();
215
227
  }
216
228
  },
217
229
  onUpdate(options) {
package/context.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { STATE, formatPaths, getPaths, unstable_createFormContext, isPrefix } from '@conform-to/dom';
2
+ import { STATE, formatPaths, getPaths, unstable_createFormContext, INTENT, isPrefix } from '@conform-to/dom';
3
3
  import { useContext, useMemo, createContext, useCallback, useSyncExternalStore, useRef } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
@@ -62,9 +62,10 @@ function updateSubjectRef(ref, name, subject, scope) {
62
62
  });
63
63
  }
64
64
  }
65
- function getMetadata(formId, state, subjectRef) {
65
+ function getMetadata(context, subjectRef, stateSnapshot) {
66
66
  var name = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
67
- var id = name ? "".concat(formId, "-").concat(name) : formId;
67
+ var id = name ? "".concat(context.formId, "-").concat(name) : context.formId;
68
+ var state = context.getState();
68
69
  return new Proxy({
69
70
  id,
70
71
  name,
@@ -104,7 +105,7 @@ function getMetadata(formId, state, subjectRef) {
104
105
  return () => new Proxy({}, {
105
106
  get(target, key, receiver) {
106
107
  if (typeof key === 'string') {
107
- return getFieldMetadata(formId, state, subjectRef, name, key);
108
+ return getFieldMetadata(context, subjectRef, stateSnapshot, name, key);
108
109
  }
109
110
  return Reflect.get(target, key, receiver);
110
111
  }
@@ -112,34 +113,40 @@ function getMetadata(formId, state, subjectRef) {
112
113
  }
113
114
  }, {
114
115
  get(target, key, receiver) {
115
- switch (key) {
116
- case 'key':
117
- case 'initialValue':
118
- case 'value':
119
- case 'valid':
120
- case 'dirty':
121
- updateSubjectRef(subjectRef, name, key, 'name');
122
- break;
123
- case 'errors':
124
- case 'allErrors':
125
- updateSubjectRef(subjectRef, name, 'error', key === 'errors' ? 'name' : 'prefix');
126
- break;
116
+ // We want to minize re-render by identifying whether the field is used in a callback only
117
+ // but there is no clear way to know if it is accessed during render or not
118
+ // if the stateSnapshot is not the latest, then it must be accessed in a callback
119
+ if (state === stateSnapshot) {
120
+ switch (key) {
121
+ case 'key':
122
+ case 'initialValue':
123
+ case 'value':
124
+ case 'valid':
125
+ case 'dirty':
126
+ updateSubjectRef(subjectRef, name, key, 'name');
127
+ break;
128
+ case 'errors':
129
+ case 'allErrors':
130
+ updateSubjectRef(subjectRef, name, 'error', key === 'errors' ? 'name' : 'prefix');
131
+ break;
132
+ }
127
133
  }
128
134
  return Reflect.get(target, key, receiver);
129
135
  }
130
136
  });
131
137
  }
132
- function getFieldMetadata(formId, state, subjectRef) {
138
+ function getFieldMetadata(context, subjectRef, stateSnapshot) {
133
139
  var prefix = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
134
140
  var key = arguments.length > 4 ? arguments[4] : undefined;
135
141
  var name = typeof key === 'undefined' ? prefix : formatPaths([...getPaths(prefix), key]);
136
- var metadata = getMetadata(formId, state, subjectRef, name);
137
142
  return new Proxy({}, {
138
143
  get(_, key, receiver) {
139
144
  var _state$constraint$nam;
145
+ var metadata = getMetadata(context, subjectRef, stateSnapshot, name);
146
+ var state = context.getState();
140
147
  switch (key) {
141
148
  case 'formId':
142
- return formId;
149
+ return context.formId;
143
150
  case 'required':
144
151
  case 'minLength':
145
152
  case 'maxLength':
@@ -154,11 +161,13 @@ function getFieldMetadata(formId, state, subjectRef) {
154
161
  return () => {
155
162
  var _state$initialValue$n;
156
163
  var initialValue = (_state$initialValue$n = state.initialValue[name]) !== null && _state$initialValue$n !== void 0 ? _state$initialValue$n : [];
157
- updateSubjectRef(subjectRef, name, 'initialValue', 'name');
164
+ if (state === stateSnapshot) {
165
+ updateSubjectRef(subjectRef, name, 'initialValue', 'name');
166
+ }
158
167
  if (!Array.isArray(initialValue)) {
159
168
  throw new Error('The initial value at the given name is not a list');
160
169
  }
161
- return Array(initialValue.length).fill(0).map((_, index) => getFieldMetadata(formId, state, subjectRef, name, index));
170
+ return Array(initialValue.length).fill(0).map((_, index) => getFieldMetadata(context, subjectRef, stateSnapshot, name, index));
162
171
  };
163
172
  }
164
173
  }
@@ -166,10 +175,11 @@ function getFieldMetadata(formId, state, subjectRef) {
166
175
  }
167
176
  });
168
177
  }
169
- function getFormMetadata(formId, state, subjectRef, context, noValidate) {
170
- var metadata = getMetadata(formId, state, subjectRef);
178
+ function getFormMetadata(context, subjectRef, stateSnapshot, noValidate) {
171
179
  return new Proxy({}, {
172
180
  get(_, key, receiver) {
181
+ var metadata = getMetadata(context, subjectRef, stateSnapshot);
182
+ var state = context.getState();
173
183
  switch (key) {
174
184
  case 'context':
175
185
  return {
@@ -203,11 +213,13 @@ function createFormContext(options) {
203
213
  submit(event) {
204
214
  var submitEvent = event.nativeEvent;
205
215
  var result = context.submit(submitEvent);
206
- if (result.submission && result.submission.status !== 'success' && result.submission.error !== null) {
207
- event.preventDefault();
216
+ if (!result.submission || result.submission.status === 'success' || result.submission.error === null) {
217
+ if (!result.formData.has(INTENT)) {
218
+ var _onSubmit;
219
+ (_onSubmit = onSubmit) === null || _onSubmit === void 0 || _onSubmit(event, result);
220
+ }
208
221
  } else {
209
- var _onSubmit;
210
- (_onSubmit = onSubmit) === null || _onSubmit === void 0 || _onSubmit(event, result);
222
+ event.preventDefault();
211
223
  }
212
224
  },
213
225
  onUpdate(options) {
package/hooks.d.ts CHANGED
@@ -23,7 +23,7 @@ export declare function useForm<Schema extends Record<string, any>, FormValue =
23
23
  FormMetadata<Schema, FormError>,
24
24
  ReturnType<FormMetadata<Schema, FormError>['getFieldset']>
25
25
  ];
26
- export declare function useFormMetadata<Schema extends Record<string, any>, FormError = string[]>(formId: FormId<Schema, FormError>, options?: {
26
+ export declare function useFormMetadata<Schema extends Record<string, any>, FormError = string[]>(formId?: FormId<Schema, FormError>, options?: {
27
27
  defaultNoValidate?: boolean;
28
28
  }): FormMetadata<Schema, FormError>;
29
29
  export declare function useField<FieldSchema, FormSchema extends Record<string, unknown> = Record<string, unknown>, FormError = string[]>(name: FieldName<FieldSchema, FormSchema, FormError>, options?: {
package/hooks.js CHANGED
@@ -40,10 +40,12 @@ function useForm(options) {
40
40
  formId
41
41
  })));
42
42
  useSafeLayoutEffect(() => {
43
+ var disconnect = context$1.observe();
43
44
  document.addEventListener('input', context$1.onInput);
44
45
  document.addEventListener('focusout', context$1.onBlur);
45
46
  document.addEventListener('reset', context$1.onReset);
46
47
  return () => {
48
+ disconnect();
47
49
  document.removeEventListener('input', context$1.onInput);
48
50
  document.removeEventListener('focusout', context$1.onBlur);
49
51
  document.removeEventListener('reset', context$1.onReset);
@@ -55,26 +57,26 @@ function useForm(options) {
55
57
  }));
56
58
  });
57
59
  var subjectRef = context.useSubjectRef();
58
- var state = context.useFormState(context$1, subjectRef);
60
+ var stateSnapshot = context.useFormState(context$1, subjectRef);
59
61
  var noValidate = useNoValidate(options.defaultNoValidate);
60
- var form = context.getFormMetadata(formId, state, subjectRef, context$1, noValidate);
62
+ var form = context.getFormMetadata(context$1, subjectRef, stateSnapshot, noValidate);
61
63
  return [form, form.getFieldset()];
62
64
  }
63
65
  function useFormMetadata(formId) {
64
66
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
65
67
  var subjectRef = context.useSubjectRef();
66
68
  var context$1 = context.useFormContext(formId);
67
- var state = context.useFormState(context$1, subjectRef);
69
+ var stateSnapshot = context.useFormState(context$1, subjectRef);
68
70
  var noValidate = useNoValidate(options.defaultNoValidate);
69
- return context.getFormMetadata(context$1.formId, state, subjectRef, context$1, noValidate);
71
+ return context.getFormMetadata(context$1, subjectRef, stateSnapshot, noValidate);
70
72
  }
71
73
  function useField(name) {
72
74
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
73
75
  var subjectRef = context.useSubjectRef();
74
76
  var context$1 = context.useFormContext(options.formId);
75
- var state = context.useFormState(context$1, subjectRef);
76
- var field = context.getFieldMetadata(context$1.formId, state, subjectRef, name);
77
- var form = context.getFormMetadata(context$1.formId, state, subjectRef, context$1, false);
77
+ var stateSnapshot = context.useFormState(context$1, subjectRef);
78
+ var field = context.getFieldMetadata(context$1, subjectRef, stateSnapshot, name);
79
+ var form = context.getFormMetadata(context$1, subjectRef, stateSnapshot, false);
78
80
  return [field, form];
79
81
  }
80
82
 
package/hooks.mjs CHANGED
@@ -36,10 +36,12 @@ function useForm(options) {
36
36
  formId
37
37
  })));
38
38
  useSafeLayoutEffect(() => {
39
+ var disconnect = context.observe();
39
40
  document.addEventListener('input', context.onInput);
40
41
  document.addEventListener('focusout', context.onBlur);
41
42
  document.addEventListener('reset', context.onReset);
42
43
  return () => {
44
+ disconnect();
43
45
  document.removeEventListener('input', context.onInput);
44
46
  document.removeEventListener('focusout', context.onBlur);
45
47
  document.removeEventListener('reset', context.onReset);
@@ -51,26 +53,26 @@ function useForm(options) {
51
53
  }));
52
54
  });
53
55
  var subjectRef = useSubjectRef();
54
- var state = useFormState(context, subjectRef);
56
+ var stateSnapshot = useFormState(context, subjectRef);
55
57
  var noValidate = useNoValidate(options.defaultNoValidate);
56
- var form = getFormMetadata(formId, state, subjectRef, context, noValidate);
58
+ var form = getFormMetadata(context, subjectRef, stateSnapshot, noValidate);
57
59
  return [form, form.getFieldset()];
58
60
  }
59
61
  function useFormMetadata(formId) {
60
62
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
61
63
  var subjectRef = useSubjectRef();
62
64
  var context = useFormContext(formId);
63
- var state = useFormState(context, subjectRef);
65
+ var stateSnapshot = useFormState(context, subjectRef);
64
66
  var noValidate = useNoValidate(options.defaultNoValidate);
65
- return getFormMetadata(context.formId, state, subjectRef, context, noValidate);
67
+ return getFormMetadata(context, subjectRef, stateSnapshot, noValidate);
66
68
  }
67
69
  function useField(name) {
68
70
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
69
71
  var subjectRef = useSubjectRef();
70
72
  var context = useFormContext(options.formId);
71
- var state = useFormState(context, subjectRef);
72
- var field = getFieldMetadata(context.formId, state, subjectRef, name);
73
- var form = getFormMetadata(context.formId, state, subjectRef, context, false);
73
+ var stateSnapshot = useFormState(context, subjectRef);
74
+ var field = getFieldMetadata(context, subjectRef, stateSnapshot, name);
75
+ var form = getFormMetadata(context, subjectRef, stateSnapshot, false);
74
76
  return [field, form];
75
77
  }
76
78
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Conform view adapter for react",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "1.0.5",
6
+ "version": "1.1.0-pre.0",
7
7
  "main": "index.js",
8
8
  "module": "index.mjs",
9
9
  "types": "index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "url": "https://github.com/edmundhung/conform/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@conform-to/dom": "1.0.5"
33
+ "@conform-to/dom": "1.1.0-pre.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/react": "^18.2.43",