Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates rescript syntax in docs #120

Merged
merged 3 commits into from
Feb 15, 2024
Merged
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
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
Loading