MobX Form Data Abstraction
Separates form state from form rendering using MobX.
by Matt McCray
HTML
<script src="https://npmcdn.com/[email protected]/lib/mobx.umd.js"></script>
<script src="https://npmcdn.com/[email protected]/index.js"></script>
<script src="https://npmcdn.com/[email protected]"></script>
<body>
<div id="app_root">One Moment..</div>
</body>
SCSS
body {
font-family: "Helvetica Neue", Sans-Serif;
padding: 25px;
h1 {
margin-bottom: 25px;
small {
color: gray;
margin-left: 1em;
}
}
}
.Property {
font-size: 12px;
&_Label {
text-align: right;
width: 100px;
display: inline-block;
margin-right: 5px;
color: dimgray;
}
&_Value {
display: inline-block;
}
}
.Field {
input {
font-size: 110%;
}
&_ErrorMessage {
color: crimson;
font-size:80%;
}
}
Babel + JSX
const { action, useStrict, observable, computed, asReference, toJS } = mobx
const { observer } = mobxReact
const { Component } = React
const DevTools = mobxDevtools.default
useStrict(true)
class FormField {
name = null
@observable initialValue = null
@observable value = null
@observable isTouched = false
@observable validators = asReference([])
@computed get isValid() {
return this.errors.length === 0
}
@computed get isDirty() {
return this.initialValue !== this.value
}
@computed get errors() {
return this.validators
.map(validate => validate(this.value, this))
.filter(result => typeof result === 'string')
}
@computed get hasErrors() {
return this.errors.length > 0
}
constructor(name, initialValue, ...validators) {
if (isEmpty(initialValue)) {
initialValue = ""
}
else if (typeof initialValue === 'function') {
validators.unshift(initialValue)
initialValue = ""
}
action('FormField Ctor', () => {
this.name = name
this.initialValue = initialValue
this.value = initialValue
this.addValidator(...validators)
})()
}
@action('addValidator')
addValidator(...validators) {
validators.forEach(validator => {
this.validators.push(validator)
})
}
@action('changeFromUI')
changeFromUI(value) {
this.value = value
this.isTouched = true
}
@action('setInitialValue')
setInitialValue(value, reset = true) {
this.initialValue = value
if (reset) {
this.value = value
this.isTouched = false
}
}
@action('reset')
reset() {
this.value = this.initialValue
this.isTouched = false
}
}
class Form {
@observable fieldNames = []
@observable fields = asReference({})
@computed get isValid() {
return this.fieldNames
.map(name => this.fields[name].isValid)
.filter(isValid => isValid === false)
.length === 0
}
@computed get isDirty() {
return...