Skip to content

Commit

Permalink
Merge pull request #47 from qiniu/v3.x
Browse files Browse the repository at this point in the history
v3
  • Loading branch information
nighca authored Mar 4, 2022
2 parents fb51187 + 2363187 commit d3c0c38
Show file tree
Hide file tree
Showing 70 changed files with 22,303 additions and 11,900 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ name: CI
on:
push:
branches:
- v2.x
- master
pull_request:
branches:
- v2.x
- master

jobs:
Expand All @@ -15,10 +17,10 @@ jobs:

strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
node-version: [16.x]

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
Expand All @@ -30,7 +32,7 @@ jobs:
env:
CI: true
- name: Collect coverage info to Coveralls
uses: coverallsapp/github-action@v1.0.1
uses: coverallsapp/github-action@1.1.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./coverage/lcov.info
18 changes: 10 additions & 8 deletions .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Generate and deploy document
uses: JamesIves/[email protected]
env:
BRANCH: gh-pages
FOLDER: docs
BUILD_SCRIPT: npm ci && npm run build:doc
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
node-version: 16.x
- name: Build docs
run: |
npm ci
npm run build:doc
- name: Deploy docs
uses: JamesIves/[email protected]
with:
branch: gh-pages
folder: dumi/dist/formstate-x
1 change: 1 addition & 0 deletions .github/workflows/tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Tag
on:
push:
branches:
- v2.x
- master

jobs:
Expand Down
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
lib
esm
coverage
docs
/lib
/esm
/coverage
.umi
.vscode
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
docs
dumi
jest.config.js
tsconfig.json
67 changes: 18 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,31 @@
[![](https://github.com/qiniu/formstate-x/workflows/Doc/badge.svg)](https://github.com/qiniu/formstate-x/actions?query=workflow%3ADoc+branch%3Amaster)
[![](https://github.com/qiniu/formstate-x/workflows/Publish/badge.svg)](https://github.com/qiniu/formstate-x/actions?query=workflow%3APublish+branch%3Amaster)

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.
formstate-x is a tool to help you manage form state, based on [MobX](https://mobx.js.org/). formstate-x provides:

What we offer:

* **Type safety**: written in [Typescript](https://typescriptlang.org), you can compose complex form state without loss of type information

![Demo](./assets/demo.gif)

* **Reactive**: based on [MobX](https://mobx.js.org), every dependency's change causes reaction automatically and you can easily react to state's change
* **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.
* **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.
* **Reactive validation**: With reactivity system of MobX, every dependency change triggers validation automatically and you can easily react to state change
* **UI-independent**: formstate-x only deals with state / data, you can easily use it with any UI library you like

### Documents

Full documents [here](https://qiniu.github.io/formstate-x).

### Install

```shell
npm i formstate-x
// or
yarn add formstate-x
```

### Usage
### Documentation

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

const foo = new FieldState('')
const bar = new FieldState(0)
const form = new FormState({ foo, bar })
### Contributing

// handle submit
async function handleSubmit(e) {
e.preventDefault()
const result = await form.validate()
if (result.hasError) {
alert('Invalid input!')
return
}
// use the validated value
await submitted = submit(result.value)
}
1. Fork the repo and clone the forked repo
2. Install dependencies

// when render (with react)
<form onSubmit={handleSubmit}>
<FooInput {...bindInput(form.$.foo)}>
<BarInput {...bindInput(form.$.bar)}>
</form>
```
```shell
npm i
```

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

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:
```shell
npm run validate
```

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.
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.
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.
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.
5. Commit and push, then create a pull request
2 changes: 2 additions & 0 deletions dumi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/src
/dist
29 changes: 29 additions & 0 deletions dumi/.umirc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import path from 'path'
import { defineConfig } from 'dumi'

const repo = 'formstate-x'

export default defineConfig({
title: repo,
favicon: 'https://qiniu.staticfile.org/favicon.ico',
logo: `/${repo}/logo.svg`,
outputPath: `dist/${repo}`,
mode: 'site',
hash: true,
// Because of using GitHub Pages
base: `/${repo}/`,
publicPath: `/${repo}/`,
navs: [
null,
{
title: 'GitHub',
path: 'https://github.com/qiniu/formstate-x',
},
],
alias: {
'formstate-x': path.join(__dirname, 'src')
},
styles: [`.__dumi-default-navbar-logo { color: #454d64 !important; }`],
mfsu: {}
// more config: https://d.umijs.org/config
})
104 changes: 104 additions & 0 deletions dumi/docs/concepts/input/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: Input
order: 1
toc: menu
---

### Input

A input is a part of an application, which collects info from user interaction. Typical inputs include:

* HTML `<input type="text" />` which collects a text string
* [MUI `Date Picker`](https://mui.com/components/date-picker/) which collects a date
* [Antd `Upload`](https://ant.design/components/upload/) which collects a file
* Some `FullNameInput` which collects someone's full name
* ...

### General Input API

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/).

In a React.js application, a [(controlled) input component](https://reactjs.org/docs/forms.html#controlled-components) always provides an API like:

```ts
type Props<T> = {
value: T
onChange: (event: ChangeEvent) => void
}
```
The prop `value` means the current input value, which will be displayed to the user.
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.
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.
An [uncontrolled input component](https://reactjs.org/docs/uncontrolled-components.html) may behave slightly different, but the flow are mostly the same.
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.
### Input API in formstate-x
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.
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.
The feedback UI, which tells users if they made a mistake, is always placed beside the input UI, too.
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.
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:
```ts
type Props = {
state: State
}
```
`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.
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:
```ts
// the input state factory
export function createState(): State {
// create state with certain validation logic
}

// the input component who accepts the state
export default function Input({ state }: { state: State }) {
// render input with value & validation info from `state`
}
```

The consumer of the input may access and imperatively control (if needed) value and
validation info through `state`.

### Composability

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:

_Below content is a pseudo-code sample. For more realistic example, you can check section [Composition](/guide/composition)._

```tsx | pure
import InputFoo, * as inputFoo from './InputFoo'
import InputBar, * as inputBar from './InputBar'

type State = Compose<inputFoo.State, inputBar.State>

export function createState(): State {
return compose(
inputFoo.createState(),
inputBar.createState()
)
}

export default function InputFooBar({ state }: { state: State }) {
return (
<>
<InputFoo state={state.foo} />
<InputBar state={state.bar} />
</>
)
}
```
Loading

0 comments on commit d3c0c38

Please sign in to comment.