@shopify/react-form
Version:
Manage React forms tersely and safely-typed with no magic using React hooks
129 lines (123 loc) • 5.24 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var react = require('react');
var reactHooks = require('@shopify/react-hooks');
var utilities = require('../utilities.js');
var dirty = require('./dirty.js');
var reset = require('./reset.js');
var submit = require('./submit.js');
var dynamiclistdirty = require('./list/dynamiclistdirty.js');
var dynamiclistreset = require('./list/dynamiclistreset.js');
/**
* 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`, `useList` or `useDynamicList`, allows you to easily build complex forms with smart defaults for common cases.
*
* ### Examples
*
*```typescript
* import React from 'react';
* import {useField, useForm} from '@shopify/react-form';
*
* function MyComponent() {
* const { fields, submit, submitting, dirty, reset, submitErrors } = useForm({
* fields: {
* title: useField('some default title'),
* },
* onSubmit: (fieldValues) => {
* return {status: "fail", errors: [{message: 'bad form data'}]}
* }
* });
*
* return (
* <form onSubmit={submit}>
* {submitting && <p className="loading">loading...</p>}
* {submitErrors.length>0 && <p className="error">submitErrors.join(', ')</p>}
* <div>
* <label for="title">Title</label>
* <input
* id="title"
* name="title"
* value={title.value}
* onChange={title.onChange}
* onBlur={title.onBlur}
* />
* {title.error && <p className="error">{title.error}</p>}
* </div>
* <button disabled={!dirty} onClick={reset}>Reset</button>
* <button type="submit" disabled={!dirty}>Submit</button>
* </form>
* );
*```
*
* @param fields - A dictionary of `Field` objects, dictionaries of `Field` objects, and lists of dictionaries of `Field` objects. Generally, you'll want these to be generated by the other hooks in this package, either `useField` or `useList`. This will be returned back out as the `fields` property of the return value.
*
* @param onSubmit - An async function to handle submission of the form. If this function returns an object of `{status: 'fail', error: FormError[]}` then the submission is considered a failure. Otherwise, it should return an object with `{status: 'success'}` and the submission will be considered a success. `useForm` will also call all client-side validation methods for the fields passed to it. The `onSubmit` handler will not be called if client validations fails.
* @param dynamicLists - optional dictionaries of `DynamicList`.
* @param makeCleanAfterSubmit
* @returns An object representing the current state of the form, with imperative methods to reset, submit, validate, and clean. Generally, the returned properties correspond 1:1 with the specific hook/utility for their functionality.
*
* @remarks
* **Building your own:** Internally, `useForm` is a convenience wrapper over `useDirty`, `useReset`, and `useSubmit`. 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.
*/
function useForm({
fields,
dynamicLists,
onSubmit,
makeCleanAfterSubmit = false
}) {
const fieldsWithLists = react.useMemo(() => {
if (dynamicLists) {
const fieldsWithList = {
...fields
};
Object.entries(dynamicLists).forEach(([key, value]) => {
fieldsWithList[key] = value.fields;
});
return fieldsWithList;
}
return fields;
}, [dynamicLists, fields]);
const dirty$1 = dirty.useDirty(fieldsWithLists);
const basicReset = reset.useReset(fieldsWithLists);
const dynamicListDirty = dynamiclistdirty.useDynamicListDirty(dynamicLists);
const dynamicListReset = dynamiclistreset.useDynamicListReset(dynamicLists);
const {
submit: submit$1,
submitting,
errors,
setErrors
} = submit.useSubmit(onSubmit, fieldsWithLists, makeCleanAfterSubmit, dynamicLists);
const reset$1 = react.useCallback(() => {
setErrors([]);
basicReset();
dynamicListReset();
}, [basicReset, dynamicListReset, setErrors]);
const fieldsRef = reactHooks.useLazyRef(() => fieldsWithLists);
fieldsRef.current = fieldsWithLists;
const dynamicListsRef = reactHooks.useLazyRef(() => dynamicLists);
const validate = react.useCallback(() => {
return utilities.validateAll(fieldsRef.current);
}, [fieldsRef]);
const makeClean = react.useCallback(() => {
utilities.makeCleanFields(fieldsRef.current);
utilities.makeCleanDynamicLists(dynamicListsRef.current);
}, [dynamicListsRef, fieldsRef]);
const form = {
fields,
dirty: dirty$1 || dynamicListDirty,
submitting,
submit: submit$1,
reset: reset$1,
validate,
makeClean,
submitErrors: errors
};
if (dynamicLists) {
return {
...form,
dynamicLists
};
}
return form;
}
exports.useForm = useForm;