diff --git a/CHANGELOG.md b/CHANGELOG.md index 079aece..cd31c5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- `useFormField` hook which registers a new input and returns its current value, error, and key ([#31](https://github.com/JBKLabs/react-form/issues/31)) +- `useFormState` hook which subscribes to changes to an existing input and returns its current value, error, and key ([#31](https://github.com/JBKLabs/react-form/issues/31)) +- `useFormContext` hook which allows access to inner workings of the library ([#31](https://github.com/JBKLabs/react-form/issues/31)) + +### Updated +- Added `revalidateInputs` callback to `Form.onChange` and `Form.onSubmit` ([#31](https://github.com/JBKLabs/react-form/issues/31)) +- Added a `changedFields` [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) to `Form.onChange` ([#31](https://github.com/JBKLabs/react-form/issues/31)) + +### Fixed +- issue where all inputs rerender anytime a single input value changes ([#31](https://github.com/JBKLabs/react-form/issues/31)) + ## [0.2.0] - 2019-10-02 ### Added - `@jbk/react-form/native` module export for react native development diff --git a/README.md b/README.md index 69cffe9..a81de93 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ This library exports the following: * `withFormHandling` * `Form` * `ValidationError` +* `useFormField` +* `useFormState` +* `useFormContext` **withFormHandling(Component, onChange)** @@ -98,11 +101,14 @@ export default withFormHandling(Input, (value) => { }); ``` -In addition to `value`, the `onChange` callback is also provided all component props. This will allow you to achieve validation similar to the following: +In addition to `value`, the `onChange` callback is also provided all component props and a method to get the current value and error for any existing form field. This will allow you to achieve validation similar to the following: ```jsx -export default withFormHandling(Input, (value, { regex }) => { - if (regex && !value.match(regex)) { +export default withFormHandling(Input, (value, props, getField) => { + const password = getField('password'); + if (props.matchPassword && value !== password.value) { + throw new ValidationError('Does not match.') + } else if (props.regex && !value.match(props.regex)) { throw new ValidationError('Invalid pattern.') } }); @@ -168,7 +174,7 @@ All components wrapped by `withFormHandling` must be nested underneath one of `r Other than props supported by html's `form`, you can provide the following props to the `Form` component: -**onSubmit({ formValid, values, resetInputs })** +**onSubmit({ formValid, values, resetInputs, revalidateInputs })** This callback function will be called anytime a submit event is fired within the `Form` component. @@ -177,6 +183,7 @@ This callback function will be called anytime a submit event is fired within the `formValid`: true or false based on all of the nested inputs' `error` props. `values`: All form values; structure is based on the value of nested inputs' `name` props. `resetInputs`: A callback function which will allow you to reset one or more inputs back to their default values. See the `Resetting Inputs` section for more information. +`revalidateInputs`: A callback function which will allow you to revalidate one or more inputs. See the `Revalidating Inputs` section for more informtion. For example, the following form: @@ -198,17 +205,20 @@ could call your provided `onSubmit` callback with: }, value: 'example2' }, - resetInputs: () => {...} + resetInputs: () => {...}, + revalidateInputs: () => {...} } ``` -**onChange({ formValid, values })** +**onChange({ formValid, values, changedFields, resetInputs, revalidateInputs })** This callback function will be called anytime a form value changes. `formValid`: true or false based on all of the nested inputs' `error` props. -`values`: All form values; structure is based on the value of nested inputs' `name` props. +`values`: All form values; structure is based on the value of nested inputs' `name` props. +`changedFields`: A [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) containing all field names whose state changed. See the `Reacting to field updates` for more information. `resetInputs`: A callback function which will allow you to reset one or more inputs back to their default values. See the `Resetting Inputs` section for more information. +`revalidateInputs`: A callback function which will allow you to revalidate one or more inputs. See the `Revalidating Inputs` section for more informtion. An example of what this might look like can be seen in the `onSubmit` section. @@ -267,6 +277,104 @@ Given the following form: * `resetInputs()` which is the same as `resetInputs(['*'])` will reset all inputs back to their default values +**Revalidating Inputs** + +In both of the provided `Form` lifecycle hooks, `onChange` and `onSubmit`, you are able to revalidate one or more inputs based of their current value via the provided `revalidateInputs(patterns)` function. + +`patterns`: An array of glob patterns which will be matched with your inputs' names. + +Given the following form: + +```jsx +
+``` + +* `revalidateInputs(['address.state', 'address.city'])` will revalidate `address.state` and `address.city` based on their current value. + +* `revalidateInputs(['username'])` will revalidate `username` based off its current value, *not* its default value `johndoe34`. + +* `revalidateInputs(['address.*'])` will revalidate all inputs **except** `username`. + +* `revalidateInputs()` which is the same as `revalidateInputs(['*'])` will revalidate the entire form. + +**Reacting to field updates** + +In the `onChange` callback, you can perform any action based off which fields changed and triggered the change event via the provided `changedFields` [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). This is useful for scenarios where the validation of an input depends on the values of other fields within the form. For example: + +```jsx +const Input = () => {...}; +const WrappedInput = withFormHandling(Input, (value, props, getField) => { + const { customValidation } = props; + if (customValidation) { + customValidation(value, props, getField); + } +}); + +const SignUpForm = () => { + const validateConfirmPassword = useCallback((value, props, getField) => { + const password = getField('password'); + if (!!password.error) throw new ValidationError(password.error); + if (password.value !== value) throw new ValidationError('Does not match.'); + }, []); + + return ( + + ); +}; +``` + +**useFormField(name, options)** + +As an alternative to `withFormHandling`, you can utilize `useFormField` to connect an input to the overall form. i.e. + +```jsx +const MyInput = ({ name, defaultValue }) => { + const { value, setValue, error, key } = useFormField(name, { + defaultValue, + validateValue: (value) => value === 'valid' ? null : 'not valid' + }); + + return ( + <> + setValue(e.target.value)} + /> + {error && {error}} + > + ); +}; +``` + +Options: +* `defaultValue`- initial value of the form field. In addition, the `resetInputs` callback provided to `Form`'s `onSubmit` and `onChange` callback will reset the input back to the value declared as `defaultValue` +* `validateValue(value)` - callback on value changes. + * returns a value `x` -> error set to `x` + * throws a `ValidationError(x)` -> error set to `x` + * throws a general `new Error()` => error set to the error object + * returns a falsy value -> error set to `null` + ## Contributors diff --git a/examples/web/.gitattributes b/examples/web/.gitattributes new file mode 100644 index 0000000..07764a7 --- /dev/null +++ b/examples/web/.gitattributes @@ -0,0 +1 @@ +* text eol=lf \ No newline at end of file diff --git a/examples/web/.gitignore b/examples/web/.gitignore index 4d29575..88b70b2 100644 --- a/examples/web/.gitignore +++ b/examples/web/.gitignore @@ -1,15 +1,11 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - # dependencies /node_modules -/.pnp -.pnp.js # testing /coverage # production -/build +/dist # misc .DS_Store @@ -21,3 +17,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.env \ No newline at end of file diff --git a/examples/web/README.md b/examples/web/README.md new file mode 100644 index 0000000..2e07f45 --- /dev/null +++ b/examples/web/README.md @@ -0,0 +1,55 @@ +# example + +This project was generated using [`@jbknowledge/create-react-app`](https://www.npmjs.com/package/@jbknowledge/create-react-app). + +## Running Locally + +- Clone this repository +- `yarn install` +- `yarn start` +- Navigate to `localhost:8080` in your web browser + +## Building + +This project can be built using `yarn build` which will generate a static website in `dist/*` + +## Deployment + +This project is built to a static website which can be hosted anywhere that format is supported. Examples include [AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html), [Azure Storage](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website), and many others. + +### Configuration + +Configuration per environment is handled through this project's `window.env` global properties, set in `public/env.js`. Below is an example `env.js` file: + +```js +window.env = { + ENABLE_DEBUG_MODE: 'false', + API_BASE_URL: 'https://someapi.com/api/v1' +}; +``` + +These values can be accessed anywhere in the project. + +Each value in `env.js` can be overridden programmatically per environment via `yarn configure`. The `configure` script provided by `@jbknowledge/react-dev` will replace each key value in `window.env` with the value of `ENV_