Skip to content

Commit

Permalink
Merge pull request #78 from MinimaHQ/update-handlers
Browse files Browse the repository at this point in the history
Remove target and change update handlers api
  • Loading branch information
Alex Fedoseev authored Apr 22, 2020
2 parents 8667685 + 1ad6343 commit e3f044a
Show file tree
Hide file tree
Showing 24 changed files with 1,076 additions and 1,164 deletions.
3 changes: 3 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# History

## 4.0.0-beta.4
* Removed targets and changed update handlers API. See: [#68](https://github.com/MinimaHQ/re-formality/issues/68) & [#78](https://github.com/MinimaHQ/re-formality/pull/78).

## 4.0.0-beta.3
* Added `ReactDom` & `ReactNative` targets and changed update handlers API. See: [#68](https://github.com/MinimaHQ/re-formality/issues/68) & [#72](https://github.com/MinimaHQ/re-formality/pull/72).

Expand Down
26 changes: 0 additions & 26 deletions docs/01-Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,6 @@ Under the hood, `re-formality` implemented as PPX. So you need to add it to both
],
```

## Targets
Library supports 2 targets:
- `ReactDom`
- `ReactNative`

By default, it's set to `ReactDom`. But you can configure it in quite flexible ways:
1. If you want to apply specific target to all modules in the build, set environment variable `FORMALITY_TARGET` to chosen target:

```shell
FORMALITY_TARGET=ReactNative bsb -clean-world -make-world
```

2. If you want to set specific target on per module basis, do this in your form module:

```reason
module MyForm = [%form
{target: ReactNative};
type input = ...;
type output = ...;
...
];
```

---

Before proceeding with actual code, we will elaborate on some core concepts that this library implements.

---
Expand Down
54 changes: 34 additions & 20 deletions docs/04-BasicUsage.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,24 +174,36 @@ Next thing to render is a text input for `email` field:
<input
value={form.input.email}
disabled={form.submitting}
onBlur={form.blurEmail}
onBlur={_ => form.blurEmail()}
onChange={
form.updateEmail((~target, input) => {
...input,
email: target##value,
})
event =>
form.updateEmail(
(input, value) => {...input, email: value},
event->ReactEvent.Form.target##value,
)
}
/>
```

The value of the field is exposed via `form.input` record. For extra safety, we disable all inputs during form submission using `form.submitting` property which is of boolean type. The next 2 functions are very important:
1. `form.blurEmail: ReactEvent.Focus.t => unit`: must be triggered from `onBlur` handler of an input field
2. `form.updateEmail: ((~target: Js.t({..}), input) => input, ReactEvent.Form.t) => unit`: must be triggered from `onChange` handler of an input field. It takes a function as an argument which takes the event target and the current form `input`, must return updated `input` record. Event target, as defined in `reason-react` bindings, is an open object (`Js.t({..})`), which means it is not type-safe but allows to access any target property you need. Most of the time you need either `target##value` or `target##checked`.
1. `form.blurEmail: unit => unit`: must be triggered from `onBlur` handler of an input field
2. `form.updateEmail: ((input, 'inputValue) => input, 'inputValue) => unit`: must be triggered from `onChange` handler of an input field. It takes 2 arguments:
- a function which takes 2 arguments—the current form `input` and updated input value of the current field—and returns updated `input` record
- an updated input value of the current field

For ReactNative users, type signatures would be a bit different since those don't include events related stuff:
The second argument—updated input value—that gets passed to the `form.updateEmail` is exactly the same value as a second argument of the callback. Why it's done this way? Why not just use this value within the callback? It is designed this way to ensure that synthetic DOM event won't be captured by the callback.

1. `form.blurEmail: unit => unit`
2. `form.updateEmail: (input => input) => unit`
```reason
// Bad
onChange={event => {
form.updateEmail(input => {
...input,
email: event->ReactEvent.Form.target##value,
});
}}
```

As you might already know, [React's `SyntheticEvent` is pooled](https://reactjs.org/docs/events.html#event-pooling). If you would capture the event in the callback (as shown above), since the callback gets triggered asynchronously, by the time it gets called, the event is already null'ed by React and it will result in a runtime error. To avoid this, we ensure that the value is extracted from event outside of the callback.

### Messages
To display feedback in UI, we can use `form.emailResult` value. It's exactly what email validator returns but wrapped in `option` type:
Expand Down Expand Up @@ -325,12 +337,13 @@ let make = () => {
<input
value={form.input.email}
disabled={form.submitting}
onBlur={form.blurEmail}
onBlur={_ => form.blurEmail()}
onChange={
form.updateEmail((~target, input) => {
...input,
email: target##value,
})
event =>
form.updateEmail(
(input, value) => {...input, email: value},
event->ReactEvent.Form.target##value,
)
}
/>
{switch (form.emailResult) {
Expand All @@ -342,12 +355,13 @@ let make = () => {
<input
value={form.input.password}
disabled={form.submitting}
onBlur={form.blurPassword}
onBlur={_ => form.blurPassword()}
onChange={
form.updatePassword((~target, input) => {
...input,
password: target##value,
})
event =>
form.updatePassword(
(input, value) => {...input, password: value},
event->ReactEvent.Form.target##value,
)
}
/>
{switch (form.passwordResult) {
Expand Down
37 changes: 20 additions & 17 deletions docs/06-Collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ Getting the input and results, as well as handling addition, removal and field u

- `form.addAuthor({name: ""})`: adds `author` entry to collection
- `form.removeAuthor(~at: index)`: removes `author` entry from collection
- `form.blurAuthorName(~at: index, ReactEvent.Focus.t)`: triggers blur in `author.name` field at index
- `form.updateAuthorName(~at: index, (~target: Js.t({..}), input) => input, ReactEvent.Form.t)`: updates `author.name` field at index
- `form.blurAuthorName(~at: index)`: triggers blur in `author.name` field at index
- `form.updateAuthorName(~at: index, (input, 'inputValue) => input, 'inputValue)`: updates `author.name` field at index
- `form.authorNameResult(~at=index)`: returns validation result for `author.name` field at index
- `form.authorsResult`: returns result of the whole collection validation, if validator exists

Expand All @@ -104,22 +104,25 @@ let form = MyForm.useForm(...);
<input
value={author.name}
disabled={form.submitting}
onBlur={form.blurAuthorName(~at=index)}
onBlur={_ => form.blurAuthorName(~at=index)}
onChange={
form.updateAuthorName(~at=index, (~target, input) =>
{
...input,
authors:
input.authors
->Array.mapWithIndex((idx, author) =>
if (idx != index) {
author;
} else {
{name: target##value};
}
),
}
)
event =>
form.updateAuthorName(
~at=index,
(input, value) => {
...input,
authors:
input.authors
->Array.mapWithIndex((idx, author) =>
if (idx != index) {
author;
} else {
{name: value};
}
),
},
event->ReactEvent.Form.target##value,
)
}
/>
<button
Expand Down
40 changes: 8 additions & 32 deletions docs/11-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,18 +268,18 @@ type interface = {
valid: unit => option(bool),
// General field
update[Field]: ((~target: Js.t({..}), input) => input, ReactEvent.Form.t) => unit,
blur[Field]: ReactEvent.Focus.t => unit,
update[Field]: ((input, 'inputValue) => input, 'inputValue) => unit,
blur[Field]: unit => unit,
[field]Result: option(result('outputValue, message)),
// Async field
update[Field]: ((~target: Js.t({..}), input) => input, ReactEvent.Form.t) => unit,
blur[Field]: ReactEvent.Focus.t => unit,
update[Field]: ((input, 'inputValue) => input, 'inputValue) => unit,
blur[Field]: unit => unit,
[field]Result: option(Formality.Async.exposedFieldStatus('outputValue, message)),
// Field of collection
update[CollectionEntry][Field]: (~at: index, (~target: Js.t({..}), input) => input, ReactEvent.Form.t) => unit,
blur[CollectionEntry][Field]: (~at: index, ReactEvent.Focus.t) => unit,
update[CollectionEntry][Field]: (~at: index, (input, 'inputValue) => input, 'inputValue) => unit,
blur[CollectionEntry][Field]: (~at: index) => unit,
[collectionEntry][Field]Result: (~at: index) => option(result('outputValue, message)),
// Collection
Expand All @@ -292,42 +292,18 @@ type interface = {
#### Update handlers
Used to update form input for a specific field.

**Target: `ReactDom`**

```reason
// Field
update[Field]: ((~target: Js.t({..}), input) => input, ReactEvent.Form.t) => unit
// Field of collection
update[CollectionEntry][Field]: (~at: index, (~target: Js.t({..}), input) => input, ReactEvent.Form.t) => unit,
```

**Target: `ReactNative`**

```reason
// Field
update[Field]: (input => input) => unit
update[Field]: ((input, 'inputValue) => input, 'inputValue) => unit
// Field of collection
update[CollectionEntry][Field]: (~at: index, input => input) => unit,
update[CollectionEntry][Field]: (~at: index, (input, 'inputValue) => input, 'inputValue) => unit,
```
<br>

#### Blur handlers
Used to notify hook on blur event for a specific field.

**Target: `ReactDom`**

```reason
// Field
blur[Field]: ReactEvent.Focus.t => unit
// Field of collection
blur[CollectionEntry][Field]: (~at: index, ReactEvent.Focus.t) => unit,
```

**Target: `ReactNative`**

```reason
// Field
blur[Field]: unit => unit
Expand Down
1 change: 1 addition & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"start": "parcel src/index.html",
"prestart": "yarn run clean && yarn run bs:build",
"build": "parcel build src/index.html",
"prebuild": "yarn run clean && yarn run bs:build",
"clean": "yarn run dist:clean && yarn run bs:clean",
Expand Down
42 changes: 23 additions & 19 deletions examples/src/BlogPostForm.re
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ let make = () => {
type_="text"
value={form.input.title}
disabled={form.submitting}
onBlur={form.blurTitle}
onChange={
form.updateTitle((~target, input) =>
{...input, title: target##value}
onBlur={_ => form.blurTitle()}
onChange={event =>
form.updateTitle(
(input, value) => {...input, title: value},
event->ReactEvent.Form.target##value,
)
}
/>
Expand Down Expand Up @@ -137,21 +138,24 @@ let make = () => {
type_="text"
value={author.name}
disabled={form.submitting}
onBlur={form.blurAuthorName(~at=index)}
onChange={
form.updateAuthorName(~at=index, (~target, input) =>
{
...input,
authors:
input.authors
->Array.mapWithIndex((idx, author) =>
if (idx != index) {
author;
} else {
{name: target##value};
}
),
}
onBlur={_ => form.blurAuthorName(~at=index)}
onChange={event =>
form.updateAuthorName(
~at=index,
(input, value) =>
{
...input,
authors:
input.authors
->Array.mapWithIndex((idx, author) =>
if (idx != index) {
author;
} else {
{name: value};
}
),
},
event->ReactEvent.Form.target##value,
)
}
/>
Expand Down
27 changes: 15 additions & 12 deletions examples/src/LoginForm.re
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ let make = () => {
type_="text"
value={form.input.email}
disabled={form.submitting}
onBlur={form.blurEmail}
onChange={
form.updateEmail((~target, input) =>
{...input, email: target##value}
onBlur={_ => form.blurEmail()}
onChange={event =>
form.updateEmail(
(input, value) => {...input, email: value},
event->ReactEvent.Form.target##value,
)
}
/>
Expand Down Expand Up @@ -104,10 +105,11 @@ let make = () => {
type_="text"
value={form.input.password}
disabled={form.submitting}
onBlur={form.blurPassword}
onChange={
form.updatePassword((~target, input) =>
{...input, password: target##value}
onBlur={_ => form.blurPassword()}
onChange={event =>
form.updatePassword(
(input, value) => {...input, password: value},
event->ReactEvent.Form.target##value,
)
}
/>
Expand Down Expand Up @@ -140,10 +142,11 @@ let make = () => {
checked={form.input.rememberMe}
disabled={form.submitting}
className="push-lg"
onBlur={form.blurRememberMe}
onChange={
form.updateRememberMe((~target, input) =>
{...input, rememberMe: target##checked}
onBlur={_ => form.blurRememberMe()}
onChange={event =>
form.updateRememberMe(
(input, value) => {...input, rememberMe: value},
event->ReactEvent.Form.target##checked,
)
}
/>
Expand Down
Loading

0 comments on commit e3f044a

Please sign in to comment.