# use-model-validation

> Store data in a model and validate that data anywhere

Latest version **0.2.6** (published 2021-11-16) · MIT license · 0 weekly downloads

## Install

```sh
npm install use-model-validation
pnpm add use-model-validation
yarn add use-model-validation
bun add use-model-validation
```

## Health

**Score 25/100 (F)** — status: abandoned.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.2.6 |
| Published | 2021-11-16 |
| First published | 2020-08-31 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 1 |
| Unpacked size | 25.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Enijar |
| Maintainers | enijar |

## Links

- npm: https://www.npmjs.com/package/use-model-validation
- Repository: https://github.com/Enijar/use-model-validation
- Homepage: https://github.com/Enijar/use-model-validation#readme
- Issues: https://github.com/Enijar/use-model-validation/issues
- npm.io page: https://npm.io/package/use-model-validation

## Dependencies (1)

- [lodash](https://npm.io/package/lodash.md) ^4.17.21

## Recent versions

- 0.2.6 (latest) — 2021-11-16
- 0.2.5 — 2021-11-16
- 0.2.4 — 2021-11-16
- 0.2.3 — 2021-11-16
- 0.2.2 — 2021-08-14
- 0.2.1 — 2021-07-16
- 0.2.0 — 2021-07-16
- 0.1.7 — 2021-07-13
- 0.1.6 — 2021-02-03
- 0.1.5 — 2021-02-03
- 0.1.4 — 2021-02-03
- 0.1.3 — 2021-02-02
- 0.1.2 — 2020-11-25
- 0.1.1 — 2020-11-07
- 0.1.0 — 2020-10-31
- … 9 more at https://npm.io/package/use-model-validation/versions

## README

# Use Model

Store data in a model and validate that data anywhere

### Installation

```bash
npm add use-model-validation
```

### Example Usage

```js
import { R, createModel } from "use-model-validation";

const model = createModel({
  rules: {
    firstName: [R.max(10, "Too long, must be :max characters or less")],
    lastName: [R.max(20, "Too long, must be :max characters or less")],
    email: [R.required("Email is required")],
    // Supports deeply nested rules
    user: {
      password: [R.required("Password is required")],
    },
  },
});

// Update the model with some data
person.update({ firstName: "James", lastName: "Craig", email: "test" });

// Validate the model, using the model's rules
const { valid, errors, data } = person.validate();

// Set model data (pass an empty object to reset data)
console.log(person.set({}));
```

### Why?

Have you ever ran into the situation where you are having to perform validation on the client and then copy/paste the same validation on the server? This library allows you to define your validation in a single place, then re-use the validation logic anywhere (i.e. on the client and server). Take a look at this, for example:

**Define a shared model (/shared/models/user-model.js)**

```js
module.exports = createModel({
  rules: {
    firstName: [
      R.required("First name is required"),
      R.max(255, "First name is too large, max characters is :max"),
    ],
    lastName: [
      R.required("Last name is required"),
      R.max(255, "Last name is too large, max characters is :max"),
    ],
    email: [
      R.required("Email name is required"),
      R.email("Email is an invalid format"),
      R.max(255, "Email is too large, max characters is :max"),
    ],
  },
});
```

**Server Route Handler (/server/actions/new-user.js)**

```js
const userModel = require("/shared/models/user-model.js");

function newUser(req, res) {
  // Make a fresh instance of the model (to avoid race conditions)
  const user = userModel.fresh(req.body);

  const { valid, errors, data } = user.validate();

  if (!valid) {
    res.status(422).json({ errors });
  }

  // Do something with `data`, e.g. save to DB

  res.status(201).json({ messages: { server: "New user created" } });
}
```

**Client UI (/client/pages/new-user.js)**

```jsx
import userModel from "/shared/models/user-model.js";

function NewUser() {
  const [errors, setErrors] = React.useState({});

  const onChange = React.useCallback((event) => {
    const { name, value } = event.target;
    userModel.update({ [name]: value });
  }, []);

  const onSubmit = React.useCallback(
    async (event) => {
      event.preventDefault();

      const { valid, errors, data } = userModel.validate();

      setErrors(errors);

      if (valid) {
        const res = await fetch("/api/new-user", {
          method: "post",
          body: JSON.stringify(data),
        });

        const body = await res.json();

        if (res.status === 422) {
          setErrors(body);
        } else {
          // Do something on success
        }
      }
    },
    [setErrors]
  );

  return (
    <form onSubmit={onSubmit}>
      <div>
        <label htmlFor="firstName">First Name</label>
        <input id="firstName" name="firstName" onChange={onChange} />
        <div>{errors?.firstName}</div>
      </div>
      <div>
        <label htmlFor="lastName">Last Name</label>
        <input id="lastName" name="lastName" onChange={onChange} />
        <div>{errors?.lastName}</div>
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" name="email" onChange={onChange} />
        <div>{errors?.email}</div>
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
```

In the above example, the model rules are validated once and the validation logic is reused on the client and server. This allows for code sharing for full-stack JS apps.

### Adding Custom Rules

You can add any custom rule to the validator by defining a function and using the `R.test` rule:

```js
import { R } from "use-model-validation";

// Custom rule to check if a field matches another field
function match(fields, message) {
  return R.test((data) => {
    return data[fields[0]] === data[fields[1]];
  }, message);
}

const newPassword = createModel({
  rules: {
    password: [R.required("This field is required")],
    passwordConfirmation: [
      R.required("This field is required"),
      match(["password", "passwordConfirmation"], "Passwords don't match"),
    ],
  },
});
```

### Rules

Documentation of built-in rules.

| Rule       | Description                                                                      | Usage                                                             |
| ---------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| required   | Check if any file, string, number, or array value has a size > 0                 | `R.required("Required")`                                          |
| min        | Check if any file, string, number, or array value has a size >= min              | `R.min(1, "Too small, must be :min or more")`                     |
| max        | Check if any file, string, number, or array value has a size <= max              | `R.min(2, "Too large, must be :max or less")`                     |
| between    | Check if any file, string, number, or array value has a size between min and max | `R.between([1, 2], "Wrong range, must be between :min and :max")` |
| test       | Check if a custom function passes                                                | `R.test((data) => data.field === "blah", "Field must be blah")`   |
| format     | Check if a value matches a format                                                | `R.format(/^[0-9]$/, "Must be a single digit number")`            |
| email      | Check if a value is a valid email                                                | `R.email("Invalid email")`                                        |
| mobileUK   | Check if a value is a valid UK mobile number                                     | `R.mobileUK("Invalid mobile number")`                             |
| mobileUS   | Check if a value is a valid US mobile number                                     | `R.mobileUS("Invalid mobile number")`                             |
| postcodeUK | Check if a value is a valid UK postcode                                          | `R.postcodeUK("Invalid postcode")`                                |
| postcodeUS | Check if a value is a valid US postcode                                          | `R.postcodeUS("Invalid postcode")`                                |

### Model API

#### set

Override data on the model, e.g.:

```js
// Delete all fields
model.set({});

// Override firstName field but leave other fields untouched
mode.set((data) => {
  data.firstName = data.firstName.toUpperCase();
  return data;
});
```

#### update

Similar to `set` except fields will not be reset, e.g.:

```js
// Update only the firstName field
model.update({ firstName: "Test" });

// Uppercase all fields
model.update((data) => {
  for (const field in data) {
    data[field].toUpperCase();
  }
  return data;
});
```

#### validate

Validate the model and return errors (object), valid (boolean), and data (object), e.g.:

```js
const { errors, valid, data } = model.validate();

if (valid) {
  console.log(data);
} else {
  console.log(errors);
}
```

#### fresh

Create a fresh instance of the model, e.g.:

```js
const newModel = model.fresh({ firstName: "New" });

const { firstName: firstName1 } = model.get();
const { firstName: firstName2 } = newModel.get();

// firstName1 will now be different from firstName2
```

#### get

Get data stored in the model, e.g.:

```js
model.set({ firstName: "Foo", lastName: "Bar" });
const data = model.get();
// data will now contain: { firstName: "Foo", lastName: "Bar" }
```

#### on/off

Add/remove event listener, e.g.:

```js
function onSet(newData) {
  console.log(newData);
}
function onUpdate(newData) {
  console.log(newData);
}
function onValidate(result) {
  console.log(result);
}

// Add/remove listeners
model.on("set", onSet);
model.off("set", onSet);
model.on("update", onUpdate);
model.off("update", onUpdate);
model.on("validate", onValidate);
model.off("validate", onValidate);
```

### Milestones

- [x] Event emitter per model
- [ ] Global event emitter for all models (created, updated, error)
- [ ] Global field rule definitions
- [ ] Global field error message definitions
- [x] Better TypeScript support
- [x] \>85% test coverage

---
_Source: https://npm.io/package/use-model-validation · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
