@conform-to/react
Advanced tools
Comparing version 0.4.0-pre.1 to 0.4.0-pre.2
@@ -43,3 +43,3 @@ import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type ListCommand, type Primitive, type Submission } from '@conform-to/dom'; | ||
*/ | ||
onValidate?: (context: FormContext<Schema>) => void; | ||
onValidate?: (context: FormContext<Schema>) => Array<[string, string]>; | ||
/** | ||
@@ -69,3 +69,3 @@ * The submit event handler of the form. It will be called | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#useform | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#useform | ||
*/ | ||
@@ -111,3 +111,3 @@ export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema>; | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldset | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#usefieldset | ||
*/ | ||
@@ -139,3 +139,3 @@ export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldsetConfig<Schema>): Fieldset<Schema>; | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldlist | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#usefieldlist | ||
*/ | ||
@@ -170,3 +170,3 @@ export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [ | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usecontrolledinput | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#usecontrolledinput | ||
*/ | ||
@@ -173,0 +173,0 @@ export declare function useControlledInput<Element extends { |
76
hooks.js
@@ -14,3 +14,3 @@ 'use strict'; | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#useform | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#useform | ||
*/ | ||
@@ -186,38 +186,42 @@ function useForm() { | ||
var formData = dom.getFormData(form, submitter); | ||
var submission = dom.parse(formData); | ||
var context = { | ||
form, | ||
formData, | ||
submission | ||
}; // Touch all fields only if the submitter is not a command button | ||
try { | ||
var formData = dom.getFormData(form, submitter); | ||
var submission = dom.parse(formData); | ||
var _context = { | ||
form, | ||
formData, | ||
submission | ||
}; // Touch all fields only if the submitter is not a command button | ||
if (!submission.type) { | ||
for (var field of form.elements) { | ||
if (dom.isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; | ||
if (submission.context === 'submit') { | ||
for (var field of form.elements) { | ||
if (dom.isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; | ||
} | ||
} | ||
} | ||
} | ||
try { | ||
if (!config.noValidate && !submitter.formNoValidate) { | ||
var _config$onValidate; | ||
var _error; | ||
(_config$onValidate = config.onValidate) === null || _config$onValidate === void 0 ? void 0 : _config$onValidate.call(config, context); | ||
if (typeof config.onValidate === 'function') { | ||
_error = config.onValidate(_context); | ||
} else { | ||
if (config.mode !== 'server-validation') { | ||
// Clear previous result | ||
dom.setFormError(form, { | ||
context: 'submit', | ||
value: {}, | ||
error: [] | ||
}); | ||
} | ||
if (!form.reportValidity()) { | ||
dom.focusFirstInvalidField(form); | ||
event.preventDefault(); | ||
} | ||
_error = dom.getFormError(form); | ||
} | ||
} catch (e) { | ||
if (e !== form) { | ||
console.warn(e); | ||
if (_error.length > 0) { | ||
submission.error.push(..._error); | ||
} | ||
} | ||
if (!event.defaultPrevented) { | ||
if (config.mode !== 'server-validation' && submission.type === 'validate') { | ||
if (!config.noValidate && !submitter.formNoValidate && dom.hasError(submission.error) || submission.context === 'validate' && config.mode !== 'server-validation') { | ||
event.preventDefault(); | ||
@@ -227,4 +231,14 @@ } else { | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, context); | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, _context); | ||
} | ||
if (event.defaultPrevented) { | ||
dom.setFormError(form, submission); | ||
if (!form.reportValidity()) { | ||
dom.focusFirstInvalidField(form); | ||
} | ||
} | ||
} catch (e) { | ||
console.warn(e); | ||
} | ||
@@ -429,3 +443,3 @@ } | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldlist | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#usefieldlist | ||
*/ | ||
@@ -574,3 +588,3 @@ function useFieldList(ref, config) { | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usecontrolledinput | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#usecontrolledinput | ||
*/ | ||
@@ -577,0 +591,0 @@ function useControlledInput(config) { |
@@ -1,3 +0,3 @@ | ||
export { type FieldsetConstraint, type Submission, hasError, isFieldElement, parse, setFormError, shouldValidate, } from '@conform-to/dom'; | ||
export { type FieldsetConstraint, type Submission, getFormError, hasError, isFieldElement, parse, shouldValidate, } from '@conform-to/dom'; | ||
export * from './hooks'; | ||
export * as conform from './helpers'; |
@@ -11,2 +11,6 @@ 'use strict'; | ||
Object.defineProperty(exports, 'getFormError', { | ||
enumerable: true, | ||
get: function () { return dom.getFormError; } | ||
}); | ||
Object.defineProperty(exports, 'hasError', { | ||
@@ -24,6 +28,2 @@ enumerable: true, | ||
}); | ||
Object.defineProperty(exports, 'setFormError', { | ||
enumerable: true, | ||
get: function () { return dom.setFormError; } | ||
}); | ||
Object.defineProperty(exports, 'shouldValidate', { | ||
@@ -30,0 +30,0 @@ enumerable: true, |
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js'; | ||
import { getSubmissionType, setFormError, focusFirstInvalidField, requestSubmit, isFieldElement, getFormData, parse, getPaths, getName, requestValidate, getFormElement, parseListCommand, updateList } from '@conform-to/dom'; | ||
import { getSubmissionType, setFormError, focusFirstInvalidField, requestSubmit, isFieldElement, getFormData, parse, getFormError, hasError, getPaths, getName, requestValidate, getFormElement, parseListCommand, updateList } from '@conform-to/dom'; | ||
import { useRef, useState, useEffect } from 'react'; | ||
@@ -10,3 +10,3 @@ import { input } from './helpers.js'; | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#useform | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#useform | ||
*/ | ||
@@ -182,38 +182,42 @@ function useForm() { | ||
var formData = getFormData(form, submitter); | ||
var submission = parse(formData); | ||
var context = { | ||
form, | ||
formData, | ||
submission | ||
}; // Touch all fields only if the submitter is not a command button | ||
try { | ||
var formData = getFormData(form, submitter); | ||
var submission = parse(formData); | ||
var _context = { | ||
form, | ||
formData, | ||
submission | ||
}; // Touch all fields only if the submitter is not a command button | ||
if (!submission.type) { | ||
for (var field of form.elements) { | ||
if (isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; | ||
if (submission.context === 'submit') { | ||
for (var field of form.elements) { | ||
if (isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; | ||
} | ||
} | ||
} | ||
} | ||
try { | ||
if (!config.noValidate && !submitter.formNoValidate) { | ||
var _config$onValidate; | ||
var _error; | ||
(_config$onValidate = config.onValidate) === null || _config$onValidate === void 0 ? void 0 : _config$onValidate.call(config, context); | ||
if (typeof config.onValidate === 'function') { | ||
_error = config.onValidate(_context); | ||
} else { | ||
if (config.mode !== 'server-validation') { | ||
// Clear previous result | ||
setFormError(form, { | ||
context: 'submit', | ||
value: {}, | ||
error: [] | ||
}); | ||
} | ||
if (!form.reportValidity()) { | ||
focusFirstInvalidField(form); | ||
event.preventDefault(); | ||
} | ||
_error = getFormError(form); | ||
} | ||
} catch (e) { | ||
if (e !== form) { | ||
console.warn(e); | ||
if (_error.length > 0) { | ||
submission.error.push(..._error); | ||
} | ||
} | ||
if (!event.defaultPrevented) { | ||
if (config.mode !== 'server-validation' && submission.type === 'validate') { | ||
if (!config.noValidate && !submitter.formNoValidate && hasError(submission.error) || submission.context === 'validate' && config.mode !== 'server-validation') { | ||
event.preventDefault(); | ||
@@ -223,4 +227,14 @@ } else { | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, context); | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, _context); | ||
} | ||
if (event.defaultPrevented) { | ||
setFormError(form, submission); | ||
if (!form.reportValidity()) { | ||
focusFirstInvalidField(form); | ||
} | ||
} | ||
} catch (e) { | ||
console.warn(e); | ||
} | ||
@@ -425,3 +439,3 @@ } | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldlist | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#usefieldlist | ||
*/ | ||
@@ -570,3 +584,3 @@ function useFieldList(ref, config) { | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usecontrolledinput | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.2/packages/conform-react/README.md#usecontrolledinput | ||
*/ | ||
@@ -573,0 +587,0 @@ function useControlledInput(config) { |
@@ -1,4 +0,4 @@ | ||
export { hasError, isFieldElement, parse, setFormError, shouldValidate } from '@conform-to/dom'; | ||
export { getFormError, hasError, isFieldElement, parse, shouldValidate } from '@conform-to/dom'; | ||
export { useControlledInput, useFieldList, useFieldset, useForm } from './hooks.js'; | ||
import * as helpers from './helpers.js'; | ||
export { helpers as conform }; |
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "0.4.0-pre.1", | ||
"version": "0.4.0-pre.2", | ||
"main": "index.js", | ||
@@ -23,3 +23,3 @@ "module": "module/index.js", | ||
"dependencies": { | ||
"@conform-to/dom": "0.4.0-pre.1" | ||
"@conform-to/dom": "0.4.0-pre.2" | ||
}, | ||
@@ -26,0 +26,0 @@ "peerDependencies": { |
263
README.md
@@ -13,4 +13,8 @@ # @conform-to/react | ||
- [useControlledInput](#usecontrolledinput) | ||
- [createValidate](#createvalidate) | ||
- [conform](#conform) | ||
- [hasError](#haserror) | ||
- [isFieldElement](#isfieldelement) | ||
- [parse](#parse) | ||
- [setFormError](#setformerror) | ||
- [shouldValidate](#shouldvalidate) | ||
@@ -25,4 +29,4 @@ <!-- /aside --> | ||
1. It lets you hook up custom validation logic into different form events. For example, revalidation will be triggered whenever something changed. | ||
2. It provides options for you to decide the best timing to start reporting errors. This could be as earliest as the user start typing, or also as late as the user try submitting the form. | ||
1. It enhances form validation with custom rules by subscribing to different DOM events and reporting the errors only when it is configured to do so. | ||
2. It unifies client and server validation in one place. | ||
3. It exposes the state of each field in the form of [data attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*), such as `data-conform-touched`, allowing flexible styling across your form without the need to manipulate the class names. | ||
@@ -34,4 +38,12 @@ | ||
function LoginForm() { | ||
const formProps = useForm({ | ||
const form = useForm({ | ||
/** | ||
* Validation mode. | ||
* Support "client-only" or "server-validation". | ||
* | ||
* Default to `client-only`. | ||
*/ | ||
mode: 'client-only', | ||
/** | ||
* Define when the error should be reported initially. | ||
@@ -45,2 +57,12 @@ * Support "onSubmit", "onChange", "onBlur". | ||
/** | ||
* An object representing the initial value of the form. | ||
*/ | ||
defaultValue: undefined; | ||
/** | ||
* An object describing the state from the last submission | ||
*/ | ||
state: undefined; | ||
/** | ||
* Enable native validation before hydation. | ||
@@ -61,4 +83,5 @@ * | ||
* A function to be called when the form should be (re)validated. | ||
* Only sync validation is supported | ||
*/ | ||
validate(form, submitter) { | ||
onValidate({ form, formData, submission }) { | ||
// ... | ||
@@ -70,3 +93,3 @@ }, | ||
*/ | ||
onSubmit(event) { | ||
onSubmit(event, { form, formData, submission }) { | ||
// ... | ||
@@ -76,9 +99,3 @@ }, | ||
return ( | ||
<form {...formProps}> | ||
<input type="email" name="email" required /> | ||
<input type="password" name="password" required /> | ||
<button type="submit">Login</button> | ||
</form> | ||
); | ||
// ... | ||
} | ||
@@ -88,3 +105,3 @@ ``` | ||
<details> | ||
<summary>What is `formProps`?</summary> | ||
<summary>What is `form.props`?</summary> | ||
@@ -95,9 +112,9 @@ It is a group of properties properties required to hook into form events. They can also be set explicitly as shown below: | ||
function RandomForm() { | ||
const formProps = useForm(); | ||
const form = useForm(); | ||
return ( | ||
<form | ||
ref={formProps.ref} | ||
onSubmit={formProps.onSubmit} | ||
noValidate={formProps.noValidate} | ||
ref={form.props.ref} | ||
onSubmit={form.props.onSubmit} | ||
noValidate={form.props.noValidate} | ||
> | ||
@@ -115,3 +132,3 @@ {/* ... */} | ||
Yes! It will fallback to native form submission if the submit event handler is omitted or the event is not default prevented. | ||
Yes! It will fallback to native form submission as long as the submit event is not default prevented. | ||
@@ -123,6 +140,6 @@ ```tsx | ||
function LoginForm() { | ||
const formProps = useForm(); | ||
const form = useForm(); | ||
return ( | ||
<Form method="post" action="/login" {...formProps}> | ||
<Form method="post" action="/login" {...form.props}> | ||
{/* ... */} | ||
@@ -137,5 +154,5 @@ </Form> | ||
<details> | ||
<summary>Is the `validate` function required?</summary> | ||
<summary>Is the `onValidate` function required?</summary> | ||
The `validate` function is not required if the validation logic can be fully covered by the [native constraints](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Constraint_validation#validation-related_attributes), e.g. **required** / **min** / **pattern** etc. | ||
The `onValidate` function is not required if the validation logic can be fully covered by the [native constraints](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Constraint_validation#validation-related_attributes), e.g. **required** / **min** / **pattern** etc. | ||
@@ -171,6 +188,6 @@ ```tsx | ||
This hook can be used to monitor the state of each field and help fields configuration. It lets you: | ||
This hook can be used to monitor the state of each field and help configuration. It lets you: | ||
1. Capturing errors at the form/fieldset level, removing the need to setup invalid handler on each field. | ||
2. Defining config in one central place. e.g. name, default value and constraint, then distributing it to each field using the [conform](#conform) helpers. | ||
2. Defining config in one central place. e.g. name, default value and constraint, then distributing it to each field with the [conform](#conform) helpers. | ||
@@ -509,3 +526,3 @@ ```tsx | ||
</fieldset> | ||
) | ||
); | ||
} | ||
@@ -516,53 +533,2 @@ ``` | ||
### createValidate | ||
This help you configure a validate function to check the validity of each fields and setup custom messages using the Constraint Validation APIs. | ||
```tsx | ||
import { useForm, createValidate } from '@conform-to/react'; | ||
export default function SignupForm() { | ||
const formProps = useForm({ | ||
validate: createValidate((field, formData) => { | ||
switch (field.name) { | ||
case 'email': | ||
if (field.validity.valueMissing) { | ||
field.setCustomValidity('Email is required'); | ||
} else if (field.validity.typeMismatch) { | ||
field.setCustomValidity('Please enter a valid email'); | ||
} else { | ||
field.setCustomValidity(''); | ||
} | ||
break; | ||
case 'password': | ||
if (field.validity.valueMissing) { | ||
field.setCustomValidity('Password is required'); | ||
} else if (field.validity.tooShort) { | ||
field.setCustomValidity( | ||
'The password should be at least 10 characters long', | ||
); | ||
} else { | ||
field.setCustomValidity(''); | ||
} | ||
break; | ||
case 'confirm-password': { | ||
if (field.validity.valueMissing) { | ||
field.setCustomValidity('Confirm Password is required'); | ||
} else if (field.value !== formData.get('password')) { | ||
field.setCustomValidity('The password does not match'); | ||
} else { | ||
field.setCustomValidity(''); | ||
} | ||
break; | ||
} | ||
} | ||
}), | ||
}); | ||
return <form {...formProps}>{/* ... */}</form>; | ||
} | ||
``` | ||
--- | ||
### conform | ||
@@ -633,1 +599,144 @@ | ||
``` | ||
### hasError | ||
This helper checks if there is any message defined in error array with the provided name. | ||
```ts | ||
import { hasError } from '@conform-to/react'; | ||
/** | ||
* Assume the error looks like this: | ||
*/ | ||
const error = [['email', 'Email is required']]; | ||
// This will log `true` | ||
console.log(hasError(error, 'email')); | ||
// This will log `false` | ||
console.log(hasError(error, 'password')); | ||
``` | ||
--- | ||
### isFieldElement | ||
This checks if the provided element is an `input` / `select` / `textarea` or `button` HTML element with type guard. Useful when you need to access the validityState of the fields and modify the validation message manually. | ||
```tsx | ||
import { isFieldElement } from '@conform-to/react'; | ||
export default function SignupForm() { | ||
const form = useForm({ | ||
onValidate({ form }) { | ||
for (const element of form.elements) { | ||
if (isFieldElement(element)) { | ||
switch (field.name) { | ||
case 'email': | ||
if (field.validity.valueMissing) { | ||
field.setCustomValidity('Email is required'); | ||
} else if (field.validity.typeMismatch) { | ||
field.setCustomValidity('Please enter a valid email'); | ||
} else { | ||
field.setCustomValidity(''); | ||
} | ||
break; | ||
case 'password': | ||
if (field.validity.valueMissing) { | ||
field.setCustomValidity('Password is required'); | ||
} else if (field.validity.tooShort) { | ||
field.setCustomValidity( | ||
'The password should be at least 10 characters long', | ||
); | ||
} else { | ||
field.setCustomValidity(''); | ||
} | ||
break; | ||
case 'confirm-password': { | ||
if (field.validity.valueMissing) { | ||
field.setCustomValidity('Confirm Password is required'); | ||
} else if (field.value !== formData.get('password')) { | ||
field.setCustomValidity('The password does not match'); | ||
} else { | ||
field.setCustomValidity(''); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
}); | ||
// ... | ||
} | ||
``` | ||
--- | ||
### parse | ||
It parses the formData based on the [naming convention](/docs/submission). | ||
```tsx | ||
import { parse } from '@conform-to/react'; | ||
const formData = new FormData(formElement); | ||
const submission = parse(formData); | ||
console.log(submission); | ||
``` | ||
--- | ||
### setFormError | ||
This helpers updates the form error based on the submission result by looping through all elements in the form and update the error with the [setCustomValidity](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity) API. | ||
```tsx | ||
import { setFormError } from '@conform-to/react'; | ||
function ExampleForm() { | ||
const form = useForm({ | ||
onValidate({ form, submission }) { | ||
const error = validate(submission); | ||
setFormError(form, { | ||
...submission, | ||
error, | ||
}); | ||
}, | ||
}); | ||
// ... | ||
} | ||
``` | ||
--- | ||
### shouldValidate | ||
This helper checks if the scope of validation includes a specific field by checking the submission: | ||
```tsx | ||
import { shouldValidate } from '@conform-to/react'; | ||
/** | ||
* The submission type and metadata give us hint on what should be valdiated. | ||
* If the type is 'validate', only the field with name matching the metadata must be validated. | ||
* | ||
* However, if the type is `undefined`, both will log true (Default submission) | ||
*/ | ||
const submission = { | ||
type: 'validate', | ||
metadata: 'email', | ||
value: {}, | ||
error: [], | ||
}; | ||
// This will log 'true' | ||
console.log(shouldValidate(submission, 'email')); | ||
// This will log 'false' | ||
console.log(shouldValidate(submission, 'password')); | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
80725
1538
727
+ Added@conform-to/dom@0.4.0-pre.2(transitive)
- Removed@conform-to/dom@0.4.0-pre.1(transitive)
Updated@conform-to/dom@0.4.0-pre.2