Skip to content

Commit

Permalink
Merge pull request #120 from EthanBonsignori/update-docs
Browse files Browse the repository at this point in the history
Updates rescript syntax in docs
  • Loading branch information
alex35mil authored Feb 15, 2024
2 parents 2d70829 + a27f772 commit e60808d
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 92 deletions.
38 changes: 19 additions & 19 deletions docs/04-BasicUsage.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,38 @@ It takes 2 steps to implement a form:
2. Render a form UI.

## Creating a form hook
Form hook can be created using `[%form]` ppx extension. It requires at least 2 things:
Form hook can be created using `%form()` ppx extension. It requires at least 2 things:

- `input` type which must be a record
- `validators` record

Let's start with the `input`:

```reason
module LoginForm = [%form
module LoginForm = %form(
type input = {
email: string,
password: string,
};
];
);
```

As mentioned in [**IO**](./03-IO.md) section, there should be an `output` type defined somewhere. If it's not provided, then under the hood it gets aliased to an `input` type. So the generated code would look like this:

```reason
module LoginForm = [%form
module LoginForm = %form(
type input = {
email: string,
password: string,
};
type output = input;
];
);
```

But since we want to deserialize form input into type-safe representation, we will provide our own `output` type with the `email` field set to `Email.t` type.

```reason
module LoginForm = [%form
module LoginForm = %form(
type input = {
email: string,
password: string,
Expand All @@ -45,7 +45,7 @@ module LoginForm = [%form
email: Email.t,
password: string,
};
];
);
```

Worth mentioning, fields in the `output` type must be the same as in `input` type. Otherwise, it would be a compile-time error.
Expand All @@ -58,10 +58,10 @@ type output = LoginData.t;

One more optional type that is involved here is `message`—the type of error messages that would be displayed in UI. If an app doesn't implement internalization, you can skip this type and it would be set to `string` (this is what we're going to do in the current example). Otherwise, feel free to use your own type here. See **[I18n](./10-I18n.md)** section for more details.

The next thing to implement is a `validators`: a record with the same set of fields as in `input`/`output`, each holds instructions on how to validate a field. Let's implement one for `email` field, assuming that somewhere in the app there is an `Email` module that defines `Email.t` type and `Email.validate` function which takes `string` and returns `result(Email.t, string)`.
The next thing to implement is a `validators`: a record with the same set of fields as in `input`/`output`, each holds instructions on how to validate a field. Let's implement one for `email` field, assuming that somewhere in the app there is an `Email` module that defines `Email.t` type and `Email.validate` function which takes `string` and returns `result<Email.t, string>`.

```reason
// Email.validate: string => result(Email.t, string)
// Email.validate: string => result<Email.t, string>
let validators = {
email: {
strategy: OnFirstSuccessOrFirstBlur,
Expand All @@ -72,7 +72,7 @@ let validators = {

First of all, you don't need to define a type for `validators`. It's already done by the ppx. In the simplest possible case, field validator record has 2 entries:
1. `strategy`: as described in **[Validation Strategies](./02-ValidationStrategies.md)** section
2. `validate`: function that takes `input` as argument and returns `result([OUTPUT_TYPE_OF_FIELD], message)`. In the `email` case, it's `result(Email.t, message)`.
2. `validate`: function that takes `input` as argument and returns `result<[OUTPUT_TYPE_OF_FIELD], message>`. In the `email` case, it's `result<Email.t, message>`.

If field shouldn't be validated, set its validator to `None`:

Expand Down Expand Up @@ -100,7 +100,7 @@ let validators = {
Looks like we're done with the first step:

```reason
module LoginForm = [%form
module LoginForm = %form(
type input = {
email: string,
password: string,
Expand All @@ -125,15 +125,15 @@ module LoginForm = [%form
},
},
};
];
);
```

## Rendering UI
The resulting module exposes the `useForm` hook that we are going to use for rendering form UI.

### `useForm` hook
```reason
[@react.component]
@react.component
let make = () => {
let form =
LoginForm.useForm(
Expand All @@ -155,7 +155,7 @@ As a result, we get a `form` record that holds everything we need to render UI.
Let's start with the `<form />` tag:

```reason
[@react.component]
@react.component
let make = () => {
let form = LoginForm.useForm(...);
Expand Down Expand Up @@ -273,9 +273,9 @@ But when submission fails, we need to display an error message in UI. So we need
Here, we need to mention `form.status`. Form hook tracks the status of the whole form which can be in the following states:

```reason
type formStatus('submissionError) =
type formStatus<'submissionError> =
| Editing
| Submitting(option('submissionError))
| Submitting(option<'submissionError>)
| Submitted
| SubmissionFailed('submissionError);
```
Expand All @@ -298,7 +298,7 @@ switch (form.status) {
The whole implementation:

```reason
module LoginForm = [%form
module LoginForm = %form(
type input = {
email: string,
password: string,
Expand All @@ -323,9 +323,9 @@ module LoginForm = [%form
},
},
};
];
);
[@react.component]
@react.component
let make = () => {
let form =
LoginForm.useForm(
Expand Down
12 changes: 6 additions & 6 deletions docs/05-AsyncValidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ To implement debounced async validations, you need to annotate your input field:

```reason
type input = {
email: [@field.async] string,
email: @field.async string,
};
```

And update the validator: in addition to the `strategy` and `validate` entries, add `validateAsync` function which takes value of `output` type of the field and returns `Js.Promise.t(result([OUTPUT_TYPE_OF_FIELD], message))`. In case of the `email` field, it would be `Js.Promise.t(result(Email.t, message))`.
And update the validator: in addition to the `strategy` and `validate` entries, add `validateAsync` function which takes value of `output` type of the field and returns `Js.Promise.t<result<[OUTPUT_TYPE_OF_FIELD], message>>`. In case of the `email` field, it would be `Js.Promise.t<result<Email.t, message>>`.

```reason
type input = {
email: [@field.async] string,
email: @field.async string,
};
type output = {
Expand Down Expand Up @@ -47,9 +47,9 @@ let validators = {
On the rendering side of things, there is only one change. The type of field result is a bit different:

```reason
type asyncFieldStatus('outputValue, 'message) =
type asyncFieldStatus<'outputValue, 'message> =
| Validating('outputValue)
| Result(result('outputValue, 'message));
| Result(result<'outputValue, 'message>);
```

So in UI it would look like this:
Expand Down Expand Up @@ -121,7 +121,7 @@ If you want to trigger async validations on blur, define mode explicitly:

```reason
type input = {
email: [@field.async {mode: OnBlur}] string,
email: @field.async({mode: OnBlur}) string,
};
```

Expand Down
28 changes: 14 additions & 14 deletions docs/06-Collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,39 @@
Collection is an array that contains sets of fields that can be added or removed from a form.

## Configuration
To define a collection, you need to annotate a field in the `input` record with `[@field.collection]` attribute:
To define a collection, you need to annotate a field in the `input` record with `@field.collection` attribute:

```reason
type input = {
authors: [@field.collection] array(author),
type rec input = {
authors: @field.collection array<author>,
}
and author = {name: string};
```

Such field has few requirements:
1. It must be of `array(entry)` type
1. It must be of `array<entry>` type
2. `entry` type must be a record which definition is inlined in the configuration module
3. `[@field.collection]` can't be combined with other attributes but each field in `entry` record can be handled as any other field in the `input` record. E.g. you can do this:
3. `@field.collection` can't be combined with other attributes but each field in `entry` record can be handled as any other field in the `input` record. E.g. you can do this:

```reason
type input = {
authors: [@field.collection] array(author),
type rec input = {
authors: @field.collection array<author>,
}
and author = {name: [@field.async] string};
and author = {name: @field.async string};
```

Also, make sure the naming is consistent. E.g. annotated `authors` field (plural) holds an array of `author` records (singular).

When the `output` type is different, the implementation might look like this:

```reason
type input = {
authors: [@field.collection] array(author),
type rec input = {
authors: @field.collection array<author>,
}
and author = {name: string};
type output = {
authors: array(author'),
type rec output = {
authors: array<author'>,
}
and author' = {name: Author.name};
```
Expand All @@ -43,9 +43,9 @@ Collection validator has the following shape:

```reason
authors: {
collection: input => result(unit, message),
collection: input => result<unit, message>,
fields: {
name: (input, ~at: int) => result([OUTPUT_TYPE_OF_FIELD], message),
name: (input, ~at: int) => result<[OUTPUT_TYPE_OF_FIELD], message>,
}
}
```
Expand Down
4 changes: 2 additions & 2 deletions docs/07-DependentFields.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type input = {
};
```

In the case above, it states: "If the value of field `a` has changed, please, re-validate field `b` as well".
In the case above, it states: "If the value of field `a` has changed, please, re-validate field `b` as well".

A real-world use-case is a form that updates a password:

Expand All @@ -35,7 +35,7 @@ type input = {
If one of the dependent fields is a field of collection, define it like this:

```reason
type input = {
type rec input = {
title: @field.deps(author.name) string,
authors: @field.collection array<author>,
}
Expand Down
12 changes: 6 additions & 6 deletions docs/08-Metadata.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Metadata
Sometimes, to perform validation and return a proper type as an output you need something else besides a form state. For example, there is an array of categories `array(Category.t)` that comes from your server and you need to validate that input value is a valid category from this array. In such cases, you can add a `metadata` type to a form config and pass a value of this type to the `useForm` hook. Then all validators will receive an additional argument of this type on each invocation.
Sometimes, to perform validation and return a proper type as an output you need something else besides a form state. For example, there is an array of categories `array<Category.t>` that comes from your server and you need to validate that input value is a valid category from this array. In such cases, you can add a `metadata` type to a form config and pass a value of this type to the `useForm` hook. Then all validators will receive an additional argument of this type on each invocation.

```reason
module Category = {
Expand All @@ -9,7 +9,7 @@ module Category = {
}
};
module Form = [%form
module Form = %form(
type input = {
category: string,
};
Expand All @@ -19,7 +19,7 @@ module Form = [%form
};
type metadata = {
categories: array(Category.t),
categories: array<Category.t>,
};
let validators = {
Expand All @@ -40,12 +40,12 @@ module Form = [%form
},
},
};
];
);
let initialInput = {category: ""};
[@react.component]
let make = (~categories: array(Category.t)) => {
@react.component
let make = (~categories: array<Category.t>) => {
let form =
Form.useForm(
~initialInput,
Expand Down
12 changes: 6 additions & 6 deletions docs/09-FormSubmission.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
`Formality` keeps track of the whole form status which can be in the following states:

```reason
type formStatus('submissionError) =
type formStatus<'submissionError> =
| Editing
| Submitting(option('submissionError))
| Submitting(option<'submissionError>)
| Submitted
| SubmissionFailed('submissionError);
```
Expand All @@ -15,15 +15,15 @@ As it's been shown in **[Basic Usage](./04-BasicUsage.md)** section, to trigger
`onSubmit` handler takes 2 arguments: `output` data and callbacks. Let's look into the latter.

```reason
type submissionCallbacks('input, 'submissionError) = {
notifyOnSuccess: option('input) => unit,
type submissionCallbacks<'input, 'submissionError> = {
notifyOnSuccess: option<'input> => unit,
notifyOnFailure: 'submissionError => unit,
reset: unit => unit,
dismissSubmissionResult: unit => unit,
};
```

### `notifyOnSuccess: option('input) => unit`
### `notifyOnSuccess: option<'input> => unit`
This callback should be triggered after successful submission if you don't want to completely reset the form but set it to `Submitted` state preserving all internal statuses. Optionally, you can pass the next `input` that would replace the current one.

### `notifyOnFailure: 'submissionError => unit`
Expand All @@ -43,7 +43,7 @@ type submissionError =
| UnexpectedServerError;
```

Then, when a response from the server is received, you can pass one of those constructors to the `notifyOnFailure` and it will be available via `form.status` variant in `SubmissionFailed(submissionError)` constructor.
Then, when a response from the server is received, you can pass one of those constructors to the `notifyOnFailure` and it will be available via `form.status` variant in `SubmissionFailed<submissionError>` constructor.

If you don't need to parse submission errors in your form, skip this type and it would be set to `unit` under the hood:

Expand Down
Loading

0 comments on commit e60808d

Please sign in to comment.