Skip to content

Commit

Permalink
Docs (RxProps): Updates the section according to the new API
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Apr 17, 2018
1 parent 1740496 commit ab6eba8
Showing 1 changed file with 94 additions and 45 deletions.
139 changes: 94 additions & 45 deletions docs/architecture/reactive-props.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,68 @@
# Reactive props

> This is a highly experimental technology and it may be changed, or removed in the future releases.
* [Specification](#specification)
* [Declaration](#declaration)
* [Delegated subscription](#delegated-subscription)
* [Reactive field props](#reactive-field-props)
* [Reactive validation rules](#reactive-validation-rules)

## Specification
*Reactive prop* - is a field's prop, which value is resolved automatically using the live subscriptions system. The latter allows to subscribe to the specific props changes of the fields referenced within the reactive prop resolver function, and re-resolve it anytime the props change.
> This is a highly experimental technology and it may change, or be removed in the future. Follow the release notes for more information.
*Reactive prop* - is a field's prop, which value is resolved automatically using the live subscriptions system. The latter allows to subscribe to props changes of another fields, and re-evaluate the reactive prop's resolver whenever the referenced props update.

> **Note:** Right now only `required` prop can be used as a reactive prop.
This is a generic concept implemented in several features of React Advanced Form. Those are listed below in this section. Each feature may utilize this concept in a different way, although the interface strives to be unified.

## Declaration
Whenever another field's prop is referenced using the exposed `fields` argument property, a live subscription for that referenced prop is created.
## Behavior
Below, there is a list of behaviors applicable to reactive props, regardless of where they are used. For consistency's sake, [Reactive field props](#reactive-field-props) interface is used in these examples.

### Safe key reference
There is no need to explicitly check for the field's existence in order to reference props of potentially non-existent fields. The interface of reactive props provides safe key reference using Immutable instances.

For example, this is the ***wrong*** way of declaring a prop subscription:

```jsx
<Input
name="fieldOne"
initialValue="foo" />
<Input
name="fieldTwo"
required={({ fields }) => {
return !!fields.fieldOne.value;
<Checkbox
required={({ get, fields }) => {
// the WRONG way of reactive prop declaration
const foo = get(['fieldOne']) && get(['fieldOne', 'value']);
const bar = fields.fieldTwo && get(['fieldTwo', 'value']);
return foo && bar;
}} />
```

Notice how `fieldTwo.required` equals a function which references the `fieldOne` field, and its `value` prop. Because of that, the `required` prop becomes reactive, re-updating each time `fieldOne.valuve` changes.

Reactive prop resolver is kept in the dedicated key of the [Field record](./field-lifecycle.md#field-record), while the reactive prop's value always equals the result of the executed resolver. For example, the value of `fieldTwo.required` would equal to `true` at the initial mount, since the resolver function returns `true`.
The ***correct*** way would be much more clean and simple:

> **Note:** It is possible to reference any props present in the [Field's state](../hoc/createField/props.md#field-state), which include `value`, `valid`, `validSync`, and many more.
### Safe key path reference
React Advanced Form uses a recursive Objecy proxy to analyze the referenced fields, therefore, it's **not needed** to ensure the safe key path reference, even if referencing deep non-existing keys. The latter are automatically ensafed, allowing you to write clear fields references.
```jsx
<Checkbox
required={({ get }) => {
/* "get" checks if "fieldOne" exists automatically */
const foo = get(['fieldOne', 'value']);
const bar = get(['fieldTwo', 'required']);

That being said, the following key path reference **will not throw**:
```js
fields.nonExistingGroup.nonExistingField.propName;
return foo && bar;
}}>
```

### Multiple fields references
It is perfectly fine to reference multiple fields within the reactive prop's resolver function:
In case the referenced field, or its prop, doesn't exist, the getter will return `undefined`.

### Multiple field references
It is possible to reference multiple fields within a single reactive prop resolver function:

```jsx
<Checkbox
name="termsAndConditions"
required={({ fields }) => {
const hasFirstName = fields.firstName.valid;
const hasLastName = fields.lastName.valid;
required={({ get }) => {
const foo = get(['firstName', 'valid']);
const bar = get(['lastName', 'required']);

return hasFirstName && hasLastName;
return foo && !bar;
}} />
```

## Delegated subscription
Reactive prop resolver can also reference the fields which haven't mounted yet at the moment of its declaration. In that case a direct subscription cannot be created.
This will create two observers, respectively, and each change of the referenced props will trigger re-evaluataion of that `required` resolver function.

### Delegated subscription
Reactive prop resolver can also reference fields which haven't mounted yet at the moment when the resolver function is declared. In that case a direct subscription cannot be created. Instead, a *delegated* subscription is created.

Nevertheless, React Advanced Form knows which fields have been referenced, regardless of the mounting status of the latter. In case the referenced field is not mounted, a *delegated subscription* is created.
The purpose of the delegated subscription is to listen for the field registration event and re-evaluate the resolver function after the mounting occurs.

Delegated subscription behaves the following way:

Expand All @@ -67,23 +73,66 @@ Delegated subscription behaves the following way:

To illustrate this, consider the next scenario:

```jsx
<Input
name="fieldTwo"
required={({ get }) => !!get(['fieldThree', 'value'])} />
<Input
name="fieldThree"
initialValue="doe" />
```

By the time `fielTwo.props.required` reactive prop is evaluated, the referenced `fieldThree` doesn't exist yet. Once the referenced field has been mounted, the resolver function (`fieldTwo.props.required`) is re-evaluated, and the value of `required` prop is updated according to the resolver logic (to `true` in the example above).

## Reactive field props
To create a reactive field prop simply pass a function as its value, and use the exposed `get` method to reference props of another fields.

> **Note:** At the moment reactive field props are supported only for the `required` prop.
### Syntax

```jsx
<FieldComponent
required={({ get }) => {
return get(['path', 'to', 'field"s', 'prop']);
}} />
```

### Usage
Whenever another field's prop is referenced using the `get` method, a live subscription for that referenced prop is created.

```jsx
<Input
name="fieldOne"
initialValue="foo" />

<Input
name="fieldTwo"
required={({ fields }) => {
const fieldOneFilled = !!fields.fieldOne.value;
const fieldThreeFilled = !!fields.fieldThree.value;
required={({ get }) => !!get(['fieldOne', 'value'])} />
```

return fieldOneFilled && fieldThreeFilled;
}} />
Reactive props resolver is never assigned as a value of the reactive prop. Instead, it is copied and executed whenever the referenced prop updates, consequentially updating the prop, which is connected to the resolver.

<Input
name="fieldThree"
initialValue="doe" />
> **Note:** It is possible to reference any props from the [Field's state](../hoc/createField/props.md#field-state) (i.e. `value`, `valid`, `validSync`, and many more).
---

## Reactive validation rules
The concept of reactive props is applicable to synchronous validation rules as well. Just as with the [Reactive field props](#reactive-field-props), whenever another field's prop is referenced within a synchronous validation rule declaration, the latter is re-evaluated each time the referenced prop updates.

### Syntax
```js
export default {
name: {
confirmPassword: ({ value, get }) => {
/**
* This reads as:
* The "confirmPassword" field is valid only when its value
* equals to "userPassword" field "value" prop.
*/
return value === get(['userPassword', 'value']);
}
}
};
```

`fieldTwo` has the reactive prop `required`, which depends on `fieldOne.value` and `fieldThree.value`. By the time the resolver is created, `fieldThree` is not mounted in the form. For that the delegated subscription is created, awaiting for the `fieldThree` to register (which, essentially, means to mount). Once the referenced fiels has been mounted, the value of `fieldTwo.required` is re-resolved into `true`, since both referenced fields have their values set.
> The syntax for type-specific validation rules is identical.

0 comments on commit ab6eba8

Please sign in to comment.