Skip to content

Commit 850445a

Browse files
committed
Finish docs (#72)
* finish docs * react as dev deps to build docs * typo * Error -> ValidationError
1 parent 67c3b93 commit 850445a

File tree

20 files changed

+13084
-11699
lines changed

20 files changed

+13084
-11699
lines changed

.github/workflows/doc.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ jobs:
1616
uses: actions/setup-node@v1
1717
with:
1818
node-version: 16.x
19-
- name: Generate and deploy document
20-
uses: JamesIves/[email protected]
21-
env:
22-
BRANCH: gh-pages
23-
FOLDER: dumi/dist
24-
BUILD_SCRIPT: npm ci && npm run build:doc
25-
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
19+
- name: Build docs
20+
run: |
21+
npm ci
22+
npm run build:doc
23+
- name: Deploy docs
24+
uses: JamesIves/[email protected]
25+
with:
26+
branch: gh-pages
27+
folder: dumi/dist/formstate-x

README.md

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,62 +5,31 @@
55
[![](https://github.com/qiniu/formstate-x/workflows/Doc/badge.svg)](https://github.com/qiniu/formstate-x/actions?query=workflow%3ADoc+branch%3Amaster)
66
[![](https://github.com/qiniu/formstate-x/workflows/Publish/badge.svg)](https://github.com/qiniu/formstate-x/actions?query=workflow%3APublish+branch%3Amaster)
77

8-
Manage the state of your form with ease, inspired by awesome [formstate](https://github.com/formstate/formstate). formstate-x maintains and validates form state for you, totally automatically.
8+
formstate-x is a tool to help you manage form state, based on [MobX](https://mobx.js.org/). formstate-x provides:
99

10-
What we offer:
11-
12-
* **Type safety**: written in [Typescript](https://typescriptlang.org), you can compose complex form state without loss of type information
13-
14-
![Demo](./assets/demo.gif)
15-
16-
* **Reactive**: based on [MobX](https://mobx.js.org), every dependency's change causes reaction automatically and you can easily react to state's change
10+
* **Composability**: Forms are composition of inputs, complex inputs are composition of simpler inputs. With composability provided by formstate-x, you can build arbitrary complex forms or input components with maximal code reuse.
11+
* **Type safety**: With Typescript, no matter how complex or dynamic the form logic is, you can get type-safe result for value, error, validator, etc.
12+
* **Reactive validation**: With reactivity system of MobX, every dependency change triggers validation automatically and you can easily react to state change
1713
* **UI-independent**: formstate-x only deals with state / data, you can easily use it with any UI library you like
1814

19-
### Documents
20-
21-
Full documents [here](https://qiniu.github.io/formstate-x).
22-
23-
### Install
24-
25-
```shell
26-
npm i formstate-x
27-
// or
28-
yarn add formstate-x
29-
```
30-
31-
### Usage
15+
### Documentation
3216

33-
```javascript
34-
import { FieldState, FormState, bindInput } from 'formstate-x'
17+
You can find full documentation [here](https://qiniu.github.io/formstate-x/).
3518

36-
const foo = new FieldState('')
37-
const bar = new FieldState(0)
38-
const form = new FormState({ foo, bar })
19+
### Contributing
3920

40-
// handle submit
41-
async function handleSubmit(e) {
42-
e.preventDefault()
43-
const result = await form.validate()
44-
if (result.hasError) {
45-
alert('Invalid input!')
46-
return
47-
}
48-
// use the validated value
49-
await submitted = submit(result.value)
50-
}
21+
1. Fork the repo and clone the forked repo
22+
2. Install dependencies
5123

52-
// when render (with react)
53-
<form onSubmit={handleSubmit}>
54-
<FooInput {...bindInput(form.$.foo)}>
55-
<BarInput {...bindInput(form.$.bar)}>
56-
</form>
57-
```
24+
```shell
25+
npm i
26+
```
5827

59-
### Comparison with [formstate](https://github.com/formstate/formstate)
28+
3. Edit the code
29+
4. Do lint & unit test
6030

61-
formstate-x provides similar APIs with [formstate](https://github.com/formstate/formstate) because we like their simplicity. formstate has a very helpful document which we will recommend you to read. But there are some points of formstate that brought inconvenience to our development, and we got disagreement with formstate in related problems. That's why we build formstate-x:
31+
```shell
32+
npm run validate
33+
```
6234

63-
1. formstate uses MobX but not embracing it, which constrained its ability (related issue: [#11](https://github.com/formstate/formstate/issues/11)). formstate-x leverages MobX's power substantially, so we can easily track all dependencies when do validation, including dynamic values or dynamic valdiators. That's also why realizing cross-field validation is extremly easy with formstat-x.
64-
2. formstate mixes validated, safe value with readable value (`$`), in some cases it's not suitable to use either `$` or `value`. formstate-x provides `value` as readable value, `$` as validated and safe value and `_value` for UI binding only.
65-
3. formstate doesn't provide a good way to extract value from `FormState` ( related issues: [#25](https://github.com/formstate/formstate/issues/25) [#28](https://github.com/formstate/formstate/issues/28)). formstate-x provides `value` as `FormState`'s serialized value, with full type info.
66-
4. formstate dosen't provide a good way to disable part of a `FormState`. `FormStateLazy` is like a hack and very different concept from `FieldState` & `FormState`. formstate-x provides `disableValidationWhen` to let you realize conditional validation with a single method call.
35+
5. Commit and push, then create a pull request

dumi/.umirc.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ const repo = 'formstate-x'
55

66
export default defineConfig({
77
title: repo,
8-
favicon: 'TODO',
9-
logo: 'TODO',
8+
favicon: 'https://qiniu.staticfile.org/favicon.ico',
9+
logo: `/${repo}/logo.svg`,
1010
outputPath: `dist/${repo}`,
1111
mode: 'site',
1212
hash: true,
@@ -23,6 +23,7 @@ export default defineConfig({
2323
alias: {
2424
'formstate-x': path.join(__dirname, 'src')
2525
},
26+
styles: [`.__dumi-default-navbar-logo { color: #454d64 !important; }`],
2627
mfsu: {}
2728
// more config: https://d.umijs.org/config
2829
})

dumi/docs/concepts/input/index.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
title: Input
3+
order: 1
4+
toc: menu
5+
---
6+
7+
### Input
8+
9+
A input is a part of an application, which collects info from user interaction. Typical inputs include:
10+
11+
* HTML `<input type="text" />` which collects a text string
12+
* [MUI `Date Picker`](https://mui.com/components/date-picker/) which collects a date
13+
* [Antd `Upload`](https://ant.design/components/upload/) which collects a file
14+
* Some `FullNameInput` which collects someone's full name
15+
* ...
16+
17+
### General Input API
18+
19+
We will introduce general inputs' API based on [React.js](https://reactjs.org/). It will not be very different in other UI frameworks like [Vue.js](https://vuejs.org/) or [Angular](https://angular.io/).
20+
21+
In a React.js application, a [(controlled) input component](https://reactjs.org/docs/forms.html#controlled-components) always provides an API like:
22+
23+
```ts
24+
type Props<T> = {
25+
value: T
26+
onChange: (event: ChangeEvent) => void
27+
}
28+
```
29+
30+
The prop `value` means the current input value, which will be displayed to the user.
31+
32+
The prop `onChange` is a handler for event `change`. When user interaction produces a new value, the input fires event `change`—the handler `onChange` will be called.
33+
34+
Typically the consumer of the input holds the value in a state. In the function `onChange`, it sets the state with the new value, which causes re-rendering, and the new value will be displayed.
35+
36+
An [uncontrolled input component](https://reactjs.org/docs/uncontrolled-components.html) may behave slightly different, but the flow are mostly the same.
37+
38+
A complex input may consist of multiple simpler inputs. MUI `Date Picker` includes a number input and a select-like date selector, A `FullNameInput` may includes a first-name input and a last-name input, etc. While no matter how complex it is, as consumer of the input, we do not need to know its implementation or UX details. All we need to do is making a convention of the input value type and then expect the input to collect it for us. That's the power of abstraction.
39+
40+
### Input API in formstate-x
41+
42+
While in forms of real applications, value of input is not the only thing we care. Users may make mistakes, we need to validate the input value and give users feedback.
43+
44+
The validation logic, which decides if a value valid, is always related with the logic of value composition and value collection—the logic of the input.
45+
46+
The feedback UI, which tells users if they made a mistake, is always placed beside the input UI, too.
47+
48+
Ideally we define inputs which extracts not only value-collection logic, but also validation and feedback logic. Like that the value can be accessed by the consumer, the validation result should be accessable for the consumer.
49+
50+
While the API above (`{ value, onChange }`) does not provide ability to encapsulate validation and feedback logic in inputs. That's why we introduce a new one:
51+
52+
```ts
53+
type Props = {
54+
state: State
55+
}
56+
```
57+
58+
`State` is a object including the input value and current validation info. For more details about it, check section [State](/concepts/state). By passing a state to an input component, information exchanges between the input and its consumer are built.
59+
60+
An important point is, the logic of (creating) state is expected to be provided by the input—that's how the input decides the validation logic. Apart from the component definition, the module of a input will also provide a state factoty, which makes the module like this:
61+
62+
```ts
63+
// the input state factory
64+
export function createState(): State {
65+
// create state with certain validation logic
66+
}
67+
68+
// the input component who accepts the state
69+
export default function Input({ state }: { state: State }) {
70+
// render input with value & validation info from `state`
71+
}
72+
```
73+
74+
The consumer of the input may access and imperatively control (if needed) value and
75+
validation info through `state`.
76+
77+
### Composability
78+
79+
Inputs are still composable. We can build a complex input based on simpler inputs. A `InputFooBar` which consists of `InputFoo` and `InputBar` may look like this:
80+
81+
_Below content is a pseudo-code sample. For more realistic example, you can check section [Composition](/guide/composition)._
82+
83+
```tsx | pure
84+
import InputFoo, * as inputFoo from './InputFoo'
85+
import InputBar, * as inputBar from './InputBar'
86+
87+
type State = Compose<inputFoo.State, inputBar.State>
88+
89+
export function createState(): State {
90+
return compose(
91+
inputFoo.createState(),
92+
inputBar.createState()
93+
)
94+
}
95+
96+
export default function InputFooBar({ state }: { state: State }) {
97+
return (
98+
<>
99+
<InputFoo state={state.foo} />
100+
<InputBar state={state.bar} />
101+
</>
102+
)
103+
}
104+
```

dumi/docs/concepts/state/index.md

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,102 @@
11
---
22
title: State
3-
order: 1
3+
order: 2
44
toc: menu
55
---
66

7-
TODO
7+
### State
8+
9+
A formstate-x state is a object which holds state (value, error, etc.) for a input.
10+
11+
In formstate-x, there are more than one state types (such as `FieldState`, `FormState`, `TransformedState`, ...), while all of them implemented the same interface `IState`. Here is the interface definition:
12+
13+
_For convenience of reading, some unimportant fields are omitted._
14+
15+
```ts
16+
/** interface for State */
17+
export interface IState<V> {
18+
/** Value in the state. */
19+
value: V
20+
/** Set `value` on change event. */
21+
onChange(value: V): void
22+
/** Set `value` imperatively. */
23+
set(value: V): void
24+
/** If activated (with auto-validation). */
25+
activated: boolean
26+
/** Current validate status. */
27+
validateStatus: ValidateStatus
28+
/** The error info of validation. */
29+
error: ValidationError
30+
/** The state's own error info, regardless of child states. */
31+
ownError: ValidationError
32+
/** Append validator(s). */
33+
withValidator(...validators: Array<Validator<V>>): this
34+
/** Fire a validation behavior imperatively. */
35+
validate(): Promise<ValidateResult<V>>
36+
/** Configure when state should be disabled. */
37+
disableWhen(predictFn: () => boolean): this
38+
}
39+
```
40+
41+
### Composability
42+
43+
Like inputs, states are composable.
44+
45+
A state may be composed of multiple child states. Its value is the composition of these child states' values and its validation result reflects all its child states' validation results.
46+
47+
A state for a complex input can be composed of its child inputs' states.
48+
49+
A state for a list input can be composed of its item inputs' states.
50+
51+
A form, which includes multiple inputs, can be considered as a complex input with a submit button. We use a formstate-x state to hold state for a form. The state for a form can be composed of its inputs' states.
52+
53+
### Value
54+
55+
A state holds the current value, and provides methods to change it.
56+
57+
A input component is expected to get state from its props. The component read the value for rendering and set the value when user interacts.
58+
59+
The input component's consumer is expected to create the state and hold it. By passing it to the input component, the consumer get the ability to "control" the input component.
60+
61+
The consumer may also read value from the state for other purposes. Typically, when a user clicks the submit button, from its state a form reads the whole value for submitting (maybe sending an HTTP request).
62+
63+
### Validation
64+
65+
Validation is the process of validating user input values.
66+
67+
Validation is important for cases like:
68+
69+
* When user inputs, we display error tips if validation not passed, so users see that and correct the input
70+
* Before form submiiting, we check if all value is valid, so invalid requests to the server can be avoided
71+
72+
That's why validation should provide such features:
73+
74+
* It should run automatically, when users changed the value, or when some other data change influcend the value validity
75+
* It should produce details such as a meaningful message, so users can get friendly hint
76+
77+
With formstate-x, we define validators and append them to states with `withValidator`. formstate-x will do validation for us. Through `validateStatus` & `error`, we can access the validate status and result.
78+
79+
For more examples of validation, check section [Validation](/guide/validation).
80+
81+
For more details about validators, check section [Validator](/concepts/validator).
82+
83+
### Activated
84+
85+
It's not user-friendly to notify a user for inputs he hasn't interact with.
86+
87+
Most forms start with all inputs empty—which values are obviously invalid. While it's not appropriate to show error tips at the beginning.
88+
89+
That's why there's a boolean field `activated` for states.
90+
91+
States will not be auto-validated until it becomes **activated**. And they will become (and stay) activated if one of these happens:
92+
93+
1. Value changed by user interactions (method `onChange()` is called).
94+
2. State imperatively validated (method `validate()` is called).
95+
96+
### Own Error
97+
98+
`ownError` & `hasOwnError` are special fields especially for composed states. You can check details about them in issue [#71](https://github.com/qiniu/formstate-x/issues/71).
99+
100+
### Disable State
101+
102+
You may find that we defined method `disableWhen` to configure when a state should be disabled. It is useful in some specific cases. You can check details in section [Disable State](/guide/advanced#disable-state).
Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
11
---
22
title: Validator
3-
order: 2
3+
order: 3
44
toc: menu
55
---
66

7-
TODO
7+
### Validator
8+
9+
A validator is a function, which takes current value as input, and returns the validation result as output. Here are some examples:
10+
11+
```ts
12+
function required(value: unknown) {
13+
return value == null && 'Required!'
14+
}
15+
16+
async function validateUsername(value: string) {
17+
if (value === '') return 'Please input username!'
18+
if (value.length > 10) return 'Too long username!'
19+
const valid = await someAsyncLogic(value)
20+
if (!valid) return 'Invalid username!'
21+
}
22+
```
23+
24+
In a validator, we check the input value to see if it's valid. If invalid, we return a message for that. Defining validation logic as such standalone functions has benefits: they are easy to reuse and easy to test.
25+
26+
### Validation Result
27+
28+
```ts
29+
/** Result of validation. */
30+
export type ValidationResult =
31+
string
32+
| null
33+
| undefined
34+
| false
35+
36+
/** Return value of validator. */
37+
export type ValidatorReturned =
38+
ValidationResult
39+
| Promise<ValidationResult>
40+
41+
/** A validator checks if given value is valid. **/
42+
export type Validator<T> = (value: T) => ValidatorReturned
43+
```
44+
45+
A validation result can be one of these two types:
46+
47+
1. A falsy value, such as `""`, `null`, `undefined`, `false`, which indicates that the input value is valid.
48+
2. A non-empty string value, such as `"Required!"`, which indicates that the input value is invalid, and the string value will be used as a meesage to notify the current user.
49+
50+
If the validation process is async, a validator—we call it "async validator"—can return a `Promise`. The promise is expected to be resolved with a validation result.
51+
52+
### Built-in Validators
53+
54+
Unlike many other form-related libraries, formstate-x provides no built-in validators.
55+
56+
Any function satisfies the definition above can be used as a validator for formstate-x. So it's easy to integrate tools like [joi](https://github.com/sideway/joi), [yup](https://github.com/jquense/yup), [validator.js](https://github.com/validatorjs/validator.js) or [async-validator](https://github.com/yiminghe/async-validator). It's also super easy to define your own validators and compose them. That's why we believe a built-in validator set is not necessary.

dumi/docs/guide/advanced/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ In section [Conditional Validation](/guide/validation#conditional-validation), w
101101

102102
However, sometimes we may add more than one validators for one state. It will not be convenient to add the `if` logic in each validator.
103103

104-
Sometimes we face complex states, which are composed with child states. It's not proper to change validators' logic of child states when writing logic of parent states. `formstate-x` provides a method `disableWhen` to help with such cases.
104+
Sometimes we face complex states, which are composed of more than one child states. It's not proper to change validators' logic of child states when writing logic of parent states. `formstate-x` provides a method `disableWhen` to help with such cases.
105105

106106
Here is a demo:
107107

0 commit comments

Comments
 (0)