Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
122 changes: 115 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ This library exports the following:
* `withFormHandling`
* `Form`
* `ValidationError`
* `useFormField`
* `useFormState`
* `useFormContext`

**withFormHandling(Component, onChange)**

Expand Down Expand Up @@ -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.')
}
});
Expand Down Expand Up @@ -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.

Expand All @@ -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:

Expand All @@ -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.

Expand Down Expand Up @@ -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
<Form
onChange={() => {...}}
>
<Input name="username" defaultValue="johndoe34" >
<Input name="address.street" >
<Input name="address.city" >
<Input name="address.state" >
<Input name="address.zip" >
</Form>
```

* `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 (
<Form
onChange={({ changedFields, revalidateInputs }) => {
if (changedFields.has('password')) {
revalidateInputs(['confirmPassword']);
}
}}
>
<WrappedInput name="password" />
<WrappedInput
name="confirmPassword"
customValidation={validateConfirmPassword}
/>
</Form>
);
};
```

**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 (
<>
<input
key={key}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{error && <span>{error}</span>}
</>
);
};
```

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

Expand Down
1 change: 1 addition & 0 deletions examples/web/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text eol=lf
8 changes: 3 additions & 5 deletions examples/web/.gitignore
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,3 +17,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

.env
55 changes: 55 additions & 0 deletions examples/web/README.md
Original file line number Diff line number Diff line change
@@ -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_<key>` if it exists. For example, if you have the following `env.js` file:

```js
window.env = {
SOME_ARG: 'local value'
};
```

And you run `yarn configure` while having the environment variable `ENV_SOME_ARG` set to `production value`. Your `env.js` file will be updated to:

```js
window.env = {
SOME_ARG: 'production value'
};
```

**NOTE**: These values are **PUBLIC**. Do not store sensitive secrets in this file.

## Linting

This project is configured with `eslint` and `prettier`.

`jbk-scripts lint` will show you what errors exist in your project. To auto fix the ones that are autofixable, run `jbk-scripts lint --fix`.
8 changes: 8 additions & 0 deletions examples/web/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["src/*"]
}
}
}
45 changes: 22 additions & 23 deletions examples/web/package.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
{
"name": "react-web",
"name": "example",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"@jbknowledge/react-form": "file:../.."
},
"main": "src/index.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"start": "jbk-scripts start",
"lint": "jbk-scripts lint src/*",
"build": "jbk-scripts build",
"configure": "jbk-scripts configure",
"eject": "jbk-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"devDependencies": {
"@jbknowledge/react-dev": "^0.4.0"
},
"devDependencies": {}
"dependencies": {
"@jbknowledge/react-form": "file:../..",
"@jbknowledge/react-models": "^0.1.0",
"core-js": "^3.4.2",
"moment": "2.24.0",
"prop-types": "15.7.2",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-router": "5.0.1",
"react-router-dom": "5.0.1",
"styled-components": "4.3.2",
"uuid": "3.3.3"
}
}
1 change: 1 addition & 0 deletions examples/web/public/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
window.env = { ENABLE_DEBUG_MODE: 'false' };
Binary file modified examples/web/public/favicon.ico
Binary file not shown.
38 changes: 21 additions & 17 deletions examples/web/public/index.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
<style>
* { box-sizing: border-box; }
html, body, #root { margin: 0; width: 100%; height: 100%; }
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">

<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

<title>example</title>
</head>

<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script src="%PUBLIC_URL%/env.js"></script>
</body>

</html>
Loading