Skip to content

Commit 67c3b93

Browse files
committed
ownError & hasOwnError for IState (#70)
* ownError & hasOwnError for IState * @OverRide for action * throw for ownError of non-recognized v2 state
1 parent 9f86e1f commit 67c3b93

File tree

12 files changed

+309
-82
lines changed

12 files changed

+309
-82
lines changed
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react'
22
import { observer } from 'mobx-react'
33
import { FieldState, FormState, TransformedState } from 'formstate-x'
4-
import { TextField, Grid } from '@mui/material'
5-
import { bindTextField } from '../../../mui-binding'
4+
import { TextField, Grid, FormHelperText } from '@mui/material'
5+
import { bindTextField, bindFormHelperText } from '../../../mui-binding'
66

77
export function createState() {
88
const state = new FormState({
@@ -16,7 +16,7 @@ export function createState() {
1616
const [first, last] = fullName.split(' ')
1717
return { first, last }
1818
}
19-
)
19+
).withValidator(notReserved)
2020
}
2121

2222
interface Props {
@@ -25,17 +25,24 @@ interface Props {
2525

2626
export default observer(function FullNameInput({ state }: Props) {
2727
return (
28-
<Grid container spacing={2}>
29-
<Grid item xs={12} sm={6}>
30-
<TextField label="First Name" margin="normal" {...bindTextField(state.$.$.first)} />
31-
</Grid>
32-
<Grid item xs={12} sm={6}>
33-
<TextField label="Last Name" margin="normal" {...bindTextField(state.$.$.last)} />
28+
<>
29+
<Grid container spacing={2}>
30+
<Grid item xs={12} sm={6}>
31+
<TextField label="First Name" margin="normal" {...bindTextField(state.$.$.first)} />
32+
</Grid>
33+
<Grid item xs={12} sm={6}>
34+
<TextField label="Last Name" margin="normal" {...bindTextField(state.$.$.last)} />
35+
</Grid>
3436
</Grid>
35-
</Grid>
37+
<FormHelperText {...bindFormHelperText(state)} />
38+
</>
3639
)
3740
})
3841

3942
function required(v: string) {
4043
if (!v) return 'Please input!'
4144
}
45+
46+
function notReserved(v: string) {
47+
if (v === 'Foo Bar') return `Name "${v}" is reserved!`
48+
}

dumi/docs/mui-binding.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,13 @@
44
*/
55

66
import { ChangeEvent } from 'react'
7-
import { ArrayFormState, FieldState, FormState, IState } from 'formstate-x'
7+
import { FieldState, IState } from 'formstate-x'
88

99
// `bindFormHelperText` bind state validate status to Materail-UI `FormHelperText`
1010
export function bindFormHelperText(state: IState) {
11-
if (state instanceof FormState || state instanceof ArrayFormState) {
12-
return {
13-
error: state.hasOwnError,
14-
children: state.ownError
15-
}
16-
}
1711
return {
18-
error: !!state.error, // TODO
19-
children: state.error
12+
error: state.hasOwnError,
13+
children: state.ownError
2014
}
2115
}
2216

@@ -37,8 +31,8 @@ export function bindTextField(state: FieldState<string>) {
3731
onChange(e: ChangeEvent<HTMLInputElement>) {
3832
state.onChange(e.target.value)
3933
},
40-
error: state.hasError,
41-
helperText: state.error
34+
error: state.hasOwnError,
35+
helperText: state.ownError
4236
}
4337
}
4438

src/adapter/v2.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ describe('fromV2', () => {
100100

101101
expect(state.validateStatus).toBe(v3.ValidateStatus.NotValidated)
102102
expect(state.error).toBe(undefined)
103+
expect(state.ownError).toBe(undefined)
103104
})
104105
it('should work well with onChange', async () => {
105106
const stateV2 = new v2.FieldState('', defaultDelay)
@@ -109,11 +110,13 @@ describe('fromV2', () => {
109110
await delay()
110111
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
111112
expect(state.error).toBe(undefined)
113+
expect(state.ownError).toBe(undefined)
112114

113115
state.onChange('')
114116
await delay()
115117
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
116118
expect(state.error).toBe('empty')
119+
expect(state.ownError).toBe('empty')
117120
})
118121
it('should work well with v2-state onChange', async () => {
119122
const stateV2 = new v2.FieldState('', defaultDelay)
@@ -182,6 +185,7 @@ describe('fromV2', () => {
182185
expect(state.dirty).toBe(false)
183186
expect(state.validateStatus).toBe(v3.ValidateStatus.NotValidated)
184187
expect(state.error).toBe(undefined)
188+
expect(state.ownError).toBe(undefined)
185189

186190
state.dispose()
187191
})
@@ -202,6 +206,7 @@ describe('fromV2', () => {
202206
await validated
203207
expect(state.validateStatus).toBe(v3.ValidateStatus.WontValidate)
204208
expect(state.error).toBe(undefined)
209+
expect(state.ownError).toBe(undefined)
205210

206211
state.onChange('123')
207212
await delay()
@@ -211,11 +216,13 @@ describe('fromV2', () => {
211216
await delay()
212217
expect(state.validateStatus).toBe(v3.ValidateStatus.WontValidate)
213218
expect(state.error).toBe(undefined)
219+
expect(state.ownError).toBe(undefined)
214220

215221
runInAction(() => options.disabled = false)
216222
await delay()
217223
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
218224
expect(state.error).toBe('empty')
225+
expect(state.ownError).toBe('empty')
219226

220227
state.dispose()
221228
})
@@ -330,6 +337,7 @@ describe('fromV2', () => {
330337

331338
expect(state.validateStatus).toBe(v3.ValidateStatus.NotValidated)
332339
expect(state.error).toBe(undefined)
340+
expect(state.ownError).toBe(undefined)
333341
})
334342
it('should work well with onChange', async () => {
335343
const stateV2 = createV2FormState('')
@@ -339,11 +347,13 @@ describe('fromV2', () => {
339347
await delay()
340348
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
341349
expect(state.error).toBe(undefined)
350+
expect(state.ownError).toBe(undefined)
342351

343352
state.onChange({ foo: '' })
344353
await delay()
345354
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
346355
expect(state.error).toBe('empty')
356+
expect(state.ownError).toBe('empty')
347357
})
348358
it('should work well with v2-state onChange', async () => {
349359
const stateV2 = createV2FormState('')
@@ -353,11 +363,13 @@ describe('fromV2', () => {
353363
await delay()
354364
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
355365
expect(state.error).toBe(undefined)
366+
expect(state.ownError).toBe(undefined)
356367

357368
stateV2.$.foo.onChange('')
358369
await delay()
359370
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
360371
expect(state.error).toBe('empty')
372+
expect(state.ownError).toBe('empty')
361373
})
362374
it('should work well with validate()', async () => {
363375
const stateV2 = createV2FormState('')
@@ -369,6 +381,7 @@ describe('fromV2', () => {
369381
expect(stateV2.error).toBe('empty')
370382
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
371383
expect(state.error).toBe('empty')
384+
expect(state.ownError).toBe('empty')
372385

373386
const validateResult1 = await validateRet1
374387
expect(validateResult1.hasError).toBe(true)
@@ -381,6 +394,7 @@ describe('fromV2', () => {
381394
expect(stateV2.error).toBe(undefined)
382395
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
383396
expect(state.error).toBe(undefined)
397+
expect(state.ownError).toBe(undefined)
384398

385399
const validateResult2 = await validateRet2
386400
expect(validateResult2.hasError).toBe(false)
@@ -403,6 +417,7 @@ describe('fromV2', () => {
403417
expect(state.dirty).toBe(false)
404418
expect(state.validateStatus).toBe(v3.ValidateStatus.NotValidated)
405419
expect(state.error).toBe(undefined)
420+
expect(state.ownError).toBe(undefined)
406421

407422
state.dispose()
408423
})
@@ -424,6 +439,7 @@ describe('fromV2', () => {
424439
await validated
425440
expect(state.validateStatus).toBe(v3.ValidateStatus.WontValidate)
426441
expect(state.error).toBe(undefined)
442+
expect(state.ownError).toBe(undefined)
427443

428444
stateV2.$.foo.onChange('123')
429445
await delay()
@@ -433,11 +449,13 @@ describe('fromV2', () => {
433449
await delay()
434450
expect(state.validateStatus).toBe(v3.ValidateStatus.WontValidate)
435451
expect(state.error).toBe(undefined)
452+
expect(state.ownError).toBe(undefined)
436453

437454
runInAction(() => options.disabled = false)
438455
await delay()
439456
expect(state.validateStatus).toBe(v3.ValidateStatus.Validated)
440457
expect(state.error).toBe('empty')
458+
expect(state.ownError).toBe('empty')
441459

442460
state.dispose()
443461
})
@@ -497,6 +515,11 @@ describe('fromV2', () => {
497515
expect(() => state.withValidator(() => 'boom')).toThrowError('Operation not supported.')
498516
expect(() => state.disableWhen(() => true)).toThrowError('Operation not supported.')
499517
})
518+
it('should throw with unknown state\'s ownError', () => {
519+
const stateV2 = new V2DumbState('')
520+
const state = fromV2(stateV2)
521+
expect(() => state.ownError).toThrowError('Operation not supported.')
522+
})
500523
it('should throw with unknown validate status', () => {
501524
const stateV2 = new V2DumbState('')
502525
stateV2._validateStatus = -1
@@ -680,6 +703,7 @@ describe('toV2', () => {
680703
}
681704
value: V
682705
dirty = false
706+
ownError = undefined
683707
error = undefined
684708
activated = false
685709
validateStatus = v3.ValidateStatus.NotValidated

src/adapter/v2.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { action, computed, makeObservable } from 'mobx'
22
import * as v2 from 'formstate-x-v2'
33
import { BaseState } from '../state'
44
import * as v3 from '..'
5+
import Disposable from '../disposable'
56

67
interface IV3StateFromV2<T extends v2.ComposibleValidatable<unknown, V>, V> extends v3.IState<V> {
78
/** The original ([email protected]) state */
@@ -23,6 +24,9 @@ class Upgrader<T extends v2.ComposibleValidatable<unknown, V>, V> extends BaseSt
2324

2425
@computed get value() { return this.stateV2.value }
2526
@computed get dirty() { return this.stateV2.dirty }
27+
@computed get ownError() {
28+
return getV3OwnError(this.stateV2)
29+
}
2630
@computed get error() { return this.stateV2.error }
2731
@computed get activated() { return this.stateV2._activated }
2832
@computed get validateStatus() {
@@ -70,7 +74,7 @@ interface IV2StateFromV3<T extends v3.IState<V>, V> extends v2.ComposibleValidat
7074
$: T
7175
}
7276

73-
class Downgrader<T extends v3.IState<V>, V> extends BaseState implements IV2StateFromV3<T, V> {
77+
class Downgrader<T extends v3.IState<V>, V> extends Disposable implements IV2StateFromV3<T, V> {
7478
constructor(private stateV3: T) {
7579
super()
7680
makeObservable(this)
@@ -85,6 +89,9 @@ class Downgrader<T extends v3.IState<V>, V> extends BaseState implements IV2Stat
8589

8690
@computed get value() { return this.stateV3.value }
8791
@computed get error() { return this.stateV3.error }
92+
@computed get hasError() {
93+
return !!this.error
94+
}
8895

8996
@computed get validationDisabled() {
9097
return this.stateV3.validateStatus === v3.ValidateStatus.WontValidate
@@ -99,9 +106,11 @@ class Downgrader<T extends v3.IState<V>, V> extends BaseState implements IV2Stat
99106
return getV2ValidateStatus(this.stateV3)
100107
}
101108

102-
// for BaseState
103-
@computed get validateStatus() {
104-
return this.stateV3.validateStatus
109+
@computed get validating() {
110+
return this.stateV3.validateStatus === v3.ValidateStatus.Validating
111+
}
112+
@computed get validated() {
113+
return this.stateV3.validateStatus === v3.ValidateStatus.Validated
105114
}
106115
}
107116

@@ -110,6 +119,16 @@ export function toV2<T extends v3.IState>(state: T): IV2StateFromV3<T, T['value'
110119
return new Downgrader(state)
111120
}
112121

122+
function getV3OwnError(stateV2: v2.ComposibleValidatable<unknown>) {
123+
if (isV2FormState(stateV2)) {
124+
return stateV2.ownError
125+
}
126+
if (isV2FieldState(stateV2)) {
127+
return stateV2.error
128+
}
129+
throwNotSupported()
130+
}
131+
113132
function getV3ValidateStatus(stateV2: v2.ComposibleValidatable<unknown>): v3.ValidateStatus {
114133
if (stateV2.validationDisabled) return v3.ValidateStatus.WontValidate
115134
switch (stateV2._validateStatus) {

0 commit comments

Comments
 (0)