@shopify/react-form
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -8,2 +8,11 @@ # Changelog | ||
## [0.2.0] | ||
### Changed | ||
- now expects errors in the form `{field?: string[], message: string}` rather than `{fieldPath?: string[], message: string}` to better match common GraphQL api error patterns. | ||
- no longer exports `useErrorPropagation` or `useValidateAll` | ||
- `useSubmit` now handles propagating errors and running client validations itself | ||
- now exports non-hook utility functions `validateAll` and `propagateErrors` | ||
## [0.1.1] | ||
@@ -10,0 +19,0 @@ |
@@ -97,3 +97,7 @@ "use strict"; | ||
var _b = tslib_1.__read(reducer_1.useFieldReducer(value), 2), state = _b[0], dispatch = _b[1]; | ||
var reset = react_1.useCallback(function () { return dispatch(reducer_1.resetAction()); }, [dispatch]); | ||
var resetActionObject = react_1.useMemo(function () { return reducer_1.resetAction(); }, []); | ||
var reset = react_1.useCallback(function () { return dispatch(resetActionObject); }, [ | ||
dispatch, | ||
resetActionObject, | ||
]); | ||
var newDefaultValue = react_1.useCallback(function (value) { return dispatch(reducer_1.newDefaultAction(value)); }, [dispatch]); | ||
@@ -100,0 +104,0 @@ var runValidation = react_1.useCallback(function (value) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var utilities_1 = require("../utilities"); | ||
var dirty_1 = require("./dirty"); | ||
var reset_1 = require("./reset"); | ||
var submit_1 = require("./submit"); | ||
var validateAll_1 = require("./validateAll"); | ||
var errorPropagation_1 = require("./errorPropagation"); | ||
/** | ||
@@ -61,5 +60,3 @@ * A custom hook for managing the state of an entire form. `useForm` wraps up many of the other hooks in this package in one API, and when combined with `useField` and `useList`, allows you to easily build complex forms with smart defaults for common cases. | ||
var reset = reset_1.useReset(fields); | ||
var validate = validateAll_1.useValidateAll(fields); | ||
var _b = submit_1.useSubmit(onSubmit, fields), submit = _b.submit, submitting = _b.submitting, errors = _b.errors, setErrors = _b.setErrors; | ||
errorPropagation_1.useErrorPropagation(fields, errors); | ||
return { | ||
@@ -70,10 +67,5 @@ fields: fields, | ||
submitErrors: errors, | ||
validate: validate, | ||
submit: function (event) { | ||
var clientErrors = validate(); | ||
if (clientErrors.length > 0) { | ||
setErrors(clientErrors); | ||
return; | ||
} | ||
submit(event); | ||
submit: submit, | ||
validate: function () { | ||
return utilities_1.validateAll(fields); | ||
}, | ||
@@ -80,0 +72,0 @@ reset: function () { |
@@ -7,3 +7,1 @@ export { useField, FieldConfig } from './field'; | ||
export { useReset } from './reset'; | ||
export { useValidateAll } from './validateAll'; | ||
export { useErrorPropagation } from './errorPropagation'; |
@@ -17,5 +17,1 @@ "use strict"; | ||
exports.useReset = reset_1.useReset; | ||
var validateAll_1 = require("./validateAll"); | ||
exports.useValidateAll = validateAll_1.useValidateAll; | ||
var errorPropagation_1 = require("./errorPropagation"); | ||
exports.useErrorPropagation = errorPropagation_1.useErrorPropagation; |
@@ -7,3 +7,3 @@ /// <reference types="react" /> | ||
errors: FormError[]; | ||
setErrors: import("react").Dispatch<import("react").SetStateAction<FormError[]>>; | ||
setErrors: (errors: FormError[]) => void; | ||
}; | ||
@@ -17,5 +17,5 @@ /** | ||
* A convenience function for `onSubmit` callbacks returning values to `useSubmit` or `useForm` | ||
* @param errors - An array of errors with the user's input. These can either include both a `fieldPath` and a `message`, in which case they will be passed down to a matching field, or just a `message`. | ||
* @param errors - An array of errors with the user's input. These can either include both a `field` and a `message`, in which case they will be passed down to a matching field, or just a `message`. | ||
* @return Returns a `SubmitResult` representing your failed form submission. | ||
*/ | ||
export declare function submitFail(errors?: FormError[]): SubmitResult; |
@@ -11,6 +11,10 @@ "use strict"; | ||
var _a = tslib_1.__read(react_1.useState(false), 2), submitting = _a[0], setSubmitting = _a[1]; | ||
var _b = tslib_1.__read(react_1.useState([]), 2), remoteErrors = _b[0], setRemoteErrors = _b[1]; | ||
var _b = tslib_1.__read(react_1.useState([]), 2), submitErrors = _b[0], setSubmitErrors = _b[1]; | ||
var setErrors = react_1.useCallback(function (errors) { | ||
setSubmitErrors(errors); | ||
utilities_1.propagateErrors(fieldBag, errors); | ||
}, [fieldBag]); | ||
function submit(event) { | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var result; | ||
var clientErrors, result; | ||
return tslib_1.__generator(this, function (_a) { | ||
@@ -22,2 +26,7 @@ switch (_a.label) { | ||
} | ||
clientErrors = utilities_1.validateAll(fieldBag); | ||
if (clientErrors.length > 0) { | ||
setErrors(clientErrors); | ||
return [2 /*return*/]; | ||
} | ||
setSubmitting(true); | ||
@@ -32,6 +41,6 @@ return [4 /*yield*/, onSubmit(getValues(fieldBag))]; | ||
if (result.status === 'fail') { | ||
setRemoteErrors(result.errors); | ||
setErrors(result.errors); | ||
} | ||
else { | ||
setRemoteErrors([]); | ||
setSubmitErrors([]); | ||
} | ||
@@ -43,3 +52,3 @@ return [2 /*return*/]; | ||
} | ||
return { submit: submit, submitting: submitting, errors: remoteErrors, setErrors: setRemoteErrors }; | ||
return { submit: submit, submitting: submitting, errors: submitErrors, setErrors: setErrors }; | ||
} | ||
@@ -73,3 +82,3 @@ exports.useSubmit = useSubmit; | ||
* A convenience function for `onSubmit` callbacks returning values to `useSubmit` or `useForm` | ||
* @param errors - An array of errors with the user's input. These can either include both a `fieldPath` and a `message`, in which case they will be passed down to a matching field, or just a `message`. | ||
* @param errors - An array of errors with the user's input. These can either include both a `field` and a `message`, in which case they will be passed down to a matching field, or just a `message`. | ||
* @return Returns a `SubmitResult` representing your failed form submission. | ||
@@ -76,0 +85,0 @@ */ |
export * from './types'; | ||
export * from './validation'; | ||
export { useField, FieldConfig, useList, useValidateAll, useErrorPropagation, useDirty, useReset, useSubmit, useForm, submitFail, submitSuccess, } from './hooks'; | ||
export { useField, FieldConfig, useList, useDirty, useReset, useSubmit, useForm, submitFail, submitSuccess, } from './hooks'; | ||
export { validateAll, propagateErrors } from './utilities'; |
@@ -8,4 +8,2 @@ "use strict"; | ||
exports.useList = hooks_1.useList; | ||
exports.useValidateAll = hooks_1.useValidateAll; | ||
exports.useErrorPropagation = hooks_1.useErrorPropagation; | ||
exports.useDirty = hooks_1.useDirty; | ||
@@ -17,1 +15,4 @@ exports.useReset = hooks_1.useReset; | ||
exports.submitSuccess = hooks_1.submitSuccess; | ||
var utilities_1 = require("./utilities"); | ||
exports.validateAll = utilities_1.validateAll; | ||
exports.propagateErrors = utilities_1.propagateErrors; |
@@ -53,3 +53,3 @@ import { ChangeEvent } from 'react'; | ||
export interface FormError { | ||
fieldPath?: string[]; | ||
field?: string[] | null; | ||
message: string; | ||
@@ -56,0 +56,0 @@ } |
import { ChangeEvent } from 'react'; | ||
import { Validates, FieldOutput, Field, Validator } from './types'; | ||
import { Validates, Validator, FieldOutput, Field, FormError } from './types'; | ||
export declare function isField<T extends object>(input: FieldOutput<T>): input is Field<T>; | ||
@@ -7,2 +7,8 @@ export declare function mapObject<Output>(input: any, mapper: (value: any, key: any) => any): Output; | ||
export declare function isChangeEvent(value: any): value is ChangeEvent<HTMLInputElement>; | ||
export declare function propagateErrors(fieldBag: { | ||
[key: string]: FieldOutput<any>; | ||
}, errors: FormError[]): void; | ||
export declare function validateAll(fieldBag: { | ||
[key: string]: FieldOutput<any>; | ||
}): FormError[]; | ||
export declare function noop(): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var get_value_1 = tslib_1.__importDefault(require("get-value")); | ||
function isField(input) { | ||
@@ -28,3 +30,54 @@ return (input.hasOwnProperty('value') && | ||
exports.isChangeEvent = isChangeEvent; | ||
function propagateErrors(fieldBag, errors) { | ||
errors.forEach(function (error) { | ||
if (error.field == null) { | ||
return; | ||
} | ||
var got = get_value_1.default(fieldBag, error.field); | ||
if (isField(got)) { | ||
if (got.error !== error.message) { | ||
got.setError(error.message); | ||
} | ||
} | ||
}); | ||
} | ||
exports.propagateErrors = propagateErrors; | ||
function validateAll(fieldBag) { | ||
var e_1, _a; | ||
var fields = Object.values(fieldBag); | ||
var errors = []; | ||
function validate(field) { | ||
var message = field.runValidation(); | ||
if (message) { | ||
errors.push({ message: message }); | ||
} | ||
} | ||
function validateDictionary(fields) { | ||
Object.values(fields).forEach(validate); | ||
} | ||
try { | ||
for (var fields_1 = tslib_1.__values(fields), fields_1_1 = fields_1.next(); !fields_1_1.done; fields_1_1 = fields_1.next()) { | ||
var item = fields_1_1.value; | ||
if (isField(item)) { | ||
validate(item); | ||
} | ||
else if (Array.isArray(item)) { | ||
item.map(validateDictionary); | ||
} | ||
else { | ||
validateDictionary(item); | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (fields_1_1 && !fields_1_1.done && (_a = fields_1.return)) _a.call(fields_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
return errors; | ||
} | ||
exports.validateAll = validateAll; | ||
function noop() { } | ||
exports.noop = noop; |
{ | ||
"name": "@shopify/react-form", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"license": "MIT", | ||
@@ -30,3 +30,3 @@ "description": "Manage react forms tersely and safely-typed with no magic using React hooks.", | ||
"devDependencies": { | ||
"@shopify/react-testing": "^1.4.3", | ||
"@shopify/react-testing": "^1.4.4", | ||
"@types/faker": "^4.1.5", | ||
@@ -33,0 +33,0 @@ "faker": "^4.1.0", |
@@ -19,7 +19,5 @@ # `@shopify/react-form` | ||
1. [useForm](#useform) | ||
1. [useValidateAll](#usevalidateall) | ||
1. [useDirty](#usedirty) | ||
1. [useReset](#usereset) | ||
1. [useSubmit](#usesubmit) | ||
1. [useErrorPropagation](#useerrorpropagation) | ||
1. [Validation](#validation) | ||
@@ -250,5 +248,2 @@ 1. [inline](#validation) | ||
// propagate submit errors back to the fields | ||
useErrorPropagation(fields, errors); | ||
const contextBar = dirty && ( | ||
@@ -790,3 +785,3 @@ <ContextualSaveBar | ||
- **Building your own:** Internally, `useForm` is a convenience wrapper over `useDirty`, `useReset`, `useValidateAll`, `useSubmit`, and `useErrorPropagation`.If you only need some of its functionality, consider building a custom hook combining a subset of them. | ||
- **Building your own:** Internally, `useForm` is a convenience wrapper over `useDirty`, `useReset`, `validateAll`, `useSubmit`, and `propagateErrors`.If you only need some of its functionality, consider building a custom hook combining a subset of them. | ||
- **Subforms:** You can have multiple `useForm`s wrapping different subsets of a group of fields. Using this you can submit subsections of the form independently and have all the error and dirty tracking logic "just work" together. | ||
@@ -796,4 +791,2 @@ | ||
#### useValidateAll | ||
#### useDirty | ||
@@ -805,4 +798,2 @@ | ||
#### useErrorPropagation | ||
Docs for these standalone hooks are coming soon. For now check out the `.d.ts` files for the API. | ||
@@ -809,0 +800,0 @@ |
87684
39
1493
870