@coxy/react-validator 2.0.7 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +145 -120
  2. package/biome.json +34 -0
  3. package/dist/index.d.mts +106 -0
  4. package/dist/index.d.ts +106 -6
  5. package/dist/index.js +2 -28
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +2 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/example/example.tsx +26 -26
  10. package/example/index.html +2 -7
  11. package/example/tsconfig.json +6 -11
  12. package/package.json +26 -36
  13. package/src/context.ts +8 -3
  14. package/src/custom-errors.test.tsx +73 -0
  15. package/src/index.test.ts +19 -4
  16. package/src/index.ts +4 -3
  17. package/src/rules.test.ts +15 -10
  18. package/src/rules.ts +15 -15
  19. package/src/types.ts +3 -2
  20. package/src/use-validator.test.tsx +4 -7
  21. package/src/use-validator.ts +3 -3
  22. package/src/validator-field.test.tsx +45 -18
  23. package/src/validator-field.tsx +44 -53
  24. package/src/validator-wrapper.test.tsx +41 -27
  25. package/src/validator-wrapper.tsx +60 -57
  26. package/src/validator.test.tsx +10 -0
  27. package/src/validator.ts +10 -10
  28. package/tsconfig.json +6 -16
  29. package/tsup.config.ts +14 -0
  30. package/.eslintignore +0 -2
  31. package/.eslintrc.js +0 -5
  32. package/.prettierrc.js +0 -10
  33. package/CHANGELOG.md +0 -49
  34. package/dist/context.d.ts +0 -7
  35. package/dist/context.js +0 -5
  36. package/dist/rules.d.ts +0 -32
  37. package/dist/rules.js +0 -60
  38. package/dist/types.d.ts +0 -24
  39. package/dist/types.js +0 -2
  40. package/dist/use-validator.d.ts +0 -4
  41. package/dist/use-validator.js +0 -22
  42. package/dist/validator-field.d.ts +0 -17
  43. package/dist/validator-field.js +0 -38
  44. package/dist/validator-wrapper.d.ts +0 -24
  45. package/dist/validator-wrapper.js +0 -57
  46. package/dist/validator.d.ts +0 -21
  47. package/dist/validator.js +0 -72
  48. package/src/jest.d.ts +0 -1
  49. package/tsconfig.build.json +0 -12
@@ -2,22 +2,19 @@
2
2
  * @jest-environment jsdom
3
3
  */
4
4
 
5
- import { unmountComponentAtNode } from 'react-dom'
6
- import { render, screen } from '@testing-library/react'
7
- import { act } from 'react-dom/test-utils'
5
+ import { act, render, screen } from '@testing-library/react'
8
6
  import { useEffect, useState } from 'react'
9
7
 
10
8
  import { rules } from './rules'
11
9
  import { useValidator } from './use-validator'
12
10
 
13
- let container
11
+ let container: HTMLDivElement | null
14
12
  beforeEach(() => {
15
13
  container = document.createElement('div')
16
14
  document.body.appendChild(container)
17
15
  })
18
16
 
19
17
  afterEach(() => {
20
- unmountComponentAtNode(container)
21
18
  container.remove()
22
19
  container = null
23
20
  })
@@ -39,8 +36,8 @@ it('check state change and hide field', () => {
39
36
 
40
37
  return (
41
38
  <>
42
- <span data-testid='test1'>{isValid ? 'true' : 'false'}</span>
43
- <span data-testid='test2'>{validateObject.message || 'true'}</span>
39
+ <span data-testid="test1">{isValid ? 'true' : 'false'}</span>
40
+ <span data-testid="test2">{validateObject.message || 'true'}</span>
44
41
  </>
45
42
  )
46
43
  }
@@ -1,7 +1,7 @@
1
+ import type { ValidatorRules } from './rules'
2
+ import type { Validity } from './types'
1
3
  import { Validator } from './validator'
2
- import { ValidatorRules } from './rules'
3
- import { Value } from './validator-field'
4
- import { Validity } from './types'
4
+ import type { Value } from './validator-field'
5
5
 
6
6
  export function useValidator(value: Value, rules: ValidatorRules): [boolean, Pick<Validity, 'message' | 'errors'>] {
7
7
  const validator = new Validator()
@@ -2,21 +2,17 @@
2
2
  * @jest-environment jsdom
3
3
  */
4
4
 
5
+ import { act, render } from '@testing-library/react'
5
6
  import { createRef, useEffect, useState } from 'react'
6
- import { render, act } from '@testing-library/react'
7
7
 
8
- import { ValidatorWrapper } from './validator-wrapper'
9
- import { ValidatorField } from './validator-field'
10
8
  import { rules } from './rules'
11
-
12
- it('render without wrapper', () => {
13
- expect(() => shallow(<ValidatorField />)).toThrowError()
14
- })
9
+ import { ValidatorField } from './validator-field'
10
+ import { ValidatorWrapper } from './validator-wrapper'
15
11
 
16
12
  it('normal render', () => {
17
13
  render(
18
14
  <ValidatorWrapper>
19
- <ValidatorField rules={[]} value='' />
15
+ <ValidatorField rules={[]} value="" />
20
16
  </ValidatorWrapper>,
21
17
  )
22
18
  })
@@ -25,7 +21,7 @@ it('check context validator', () => {
25
21
  const validator = createRef<ValidatorWrapper>()
26
22
  render(
27
23
  <ValidatorWrapper ref={validator}>
28
- <ValidatorField rules={[]} />
24
+ <ValidatorField rules={[]} value="" />
29
25
  </ValidatorWrapper>,
30
26
  )
31
27
 
@@ -42,10 +38,10 @@ it('check failed validation', () => {
42
38
  render(
43
39
  <>
44
40
  <ValidatorWrapper ref={validator1}>
45
- <ValidatorField rules={rules.email} value='test' />
41
+ <ValidatorField rules={rules.email} value="test" />
46
42
  </ValidatorWrapper>
47
43
  <ValidatorWrapper ref={validator2}>
48
- <ValidatorField rules={rules.email} value='' />
44
+ <ValidatorField rules={rules.email} value="" />
49
45
  </ValidatorWrapper>
50
46
  </>,
51
47
  )
@@ -83,8 +79,8 @@ it('check state change and hide field', () => {
83
79
 
84
80
  return (
85
81
  <ValidatorWrapper ref={validator1}>
86
- <ValidatorField rules={rules.email} value='test' />
87
- {st && <ValidatorField rules={rules.email} value='' />}
82
+ <ValidatorField rules={rules.email} value="test" />
83
+ {st && <ValidatorField rules={rules.email} value="" />}
88
84
  </ValidatorWrapper>
89
85
  )
90
86
  }
@@ -104,7 +100,7 @@ it('check success validation', () => {
104
100
  const validator = createRef<ValidatorWrapper>()
105
101
  render(
106
102
  <ValidatorWrapper ref={validator}>
107
- <ValidatorField rules={rules.email} value='email@email.com' />
103
+ <ValidatorField rules={rules.email} value="email@email.com" />
108
104
  </ValidatorWrapper>,
109
105
  )
110
106
 
@@ -118,7 +114,7 @@ it('check success validation fot child function', () => {
118
114
  const validator = createRef<ValidatorWrapper>()
119
115
  render(
120
116
  <ValidatorWrapper ref={validator}>
121
- <ValidatorField rules={rules.email} value='email@email.com'>
117
+ <ValidatorField rules={rules.email} value="email@email.com">
122
118
  {({ isValid, message }) => <>{!isValid && <div>{message}</div>}</>}
123
119
  </ValidatorField>
124
120
  </ValidatorWrapper>,
@@ -134,13 +130,13 @@ it('check custom rule message function', () => {
134
130
  const validator = createRef<ValidatorWrapper>()
135
131
  const rule = [
136
132
  {
137
- rule: (value) => value !== 'test',
138
- message: (value) => `test message ${value}`,
133
+ rule: (value: string) => value !== 'test',
134
+ message: (value: string) => `test message ${value}`,
139
135
  },
140
136
  ]
141
137
  render(
142
138
  <ValidatorWrapper ref={validator}>
143
- <ValidatorField rules={rule} value='test'>
139
+ <ValidatorField rules={rule} value="test">
144
140
  {({ isValid, message }) => <>{!isValid && <div>{message}</div>}</>}
145
141
  </ValidatorField>
146
142
  </ValidatorWrapper>,
@@ -151,3 +147,34 @@ it('check custom rule message function', () => {
151
147
  expect(validateResult.isValid).toBe(false)
152
148
  expect(validateResult.message).toBe('test message test')
153
149
  })
150
+
151
+ jest.useFakeTimers()
152
+
153
+ it('re-renders the same field to cover handleRef initialization false branch and else-validate path', () => {
154
+ const validator = createRef<ValidatorWrapper>()
155
+
156
+ function Comp() {
157
+ const [val, setVal] = useState('')
158
+ useEffect(() => {
159
+ setTimeout(() => {
160
+ act(() => setVal('abc'))
161
+ }, 50)
162
+ }, [])
163
+ return (
164
+ <ValidatorWrapper ref={validator}>
165
+ <ValidatorField id="rerender" rules={rules.notEmpty} value={val}>
166
+ {() => null}
167
+ </ValidatorField>
168
+ </ValidatorWrapper>
169
+ )
170
+ }
171
+
172
+ render(<Comp />)
173
+ // initial: invalid
174
+ let field = validator.current.getField('rerender')
175
+ expect(field.validate().isValid).toBe(false)
176
+
177
+ jest.runAllTimers()
178
+ field = validator.current.getField('rerender')
179
+ expect(field.validate().isValid).toBe(true)
180
+ })
@@ -1,68 +1,59 @@
1
- import { Component, ReactNode } from 'react'
2
- import { Validity } from 'types'
1
+ import { forwardRef, type ReactNode, useContext, useEffect, useRef } from 'react'
2
+ import type { FieldParams, Validity } from 'types'
3
3
 
4
- import { Context } from './context'
4
+ import { Context, type RegisteredFieldHandle } from './context'
5
5
  import { Field } from './validator'
6
- import { ValidatorRules } from './rules'
7
6
 
7
+ // biome-ignore lint/suspicious/noExplicitAny: <need>
8
8
  export type Value = any
9
9
 
10
10
  type Fn = (validity: Validity, value: Value) => ReactNode
11
11
 
12
- interface Props {
13
- rules?: ValidatorRules
14
- required?: boolean
15
- value?: Value
16
- id?: string | number
12
+ type Props = FieldParams & {
17
13
  children?: ReactNode | Fn
18
- unregisterField: (val: any) => void
19
- registerField: (val: any) => void
20
- customErrors: Array<Validity>
21
14
  }
22
15
 
23
- class ValidationFieldWrapper extends Component<Props> {
24
- componentWillUnmount() {
25
- this.props.unregisterField(this)
26
- }
27
-
28
- componentDidMount() {
29
- this.props.registerField(this)
16
+ export const ValidatorField = forwardRef<unknown, Props>(function ValidatorField(props: Props, _ref) {
17
+ const { children, value } = props
18
+ const { customErrors, registerField, unregisterField } = useContext(Context)
19
+
20
+ const propsRef = useRef(props)
21
+ propsRef.current = props
22
+
23
+ const customErrorsRef = useRef(customErrors)
24
+ customErrorsRef.current = customErrors
25
+
26
+ const handleRef = useRef<RegisteredFieldHandle | null>(null)
27
+ if (!handleRef.current) {
28
+ handleRef.current = {
29
+ get props() {
30
+ return propsRef.current
31
+ },
32
+ validate: () => {
33
+ const curr = propsRef.current
34
+ const customError = customErrorsRef.current.find((item) => item.id === curr.id)
35
+ if (customError) {
36
+ return customError
37
+ }
38
+ const field = new Field({
39
+ rules: curr.rules,
40
+ required: curr.required,
41
+ value: curr.value,
42
+ id: curr.id,
43
+ })
44
+ return field.validate()
45
+ },
46
+ }
30
47
  }
31
48
 
32
- validate(): Validity {
33
- const props = this.props
34
- const customError = (props.customErrors || []).find((item) => item.id === props.id)
35
- if (customError) {
36
- return customError
49
+ useEffect(() => {
50
+ registerField(handleRef.current as RegisteredFieldHandle)
51
+ return () => {
52
+ unregisterField(handleRef.current as RegisteredFieldHandle)
37
53
  }
54
+ }, [registerField, unregisterField])
38
55
 
39
- const field = new Field({
40
- rules: props.rules,
41
- required: props.required,
42
- value: props.value,
43
- id: props.id,
44
- })
45
- return field.validate()
46
- }
47
-
48
- render() {
49
- const { children, value } = this.props
50
- const validity = this.validate()
51
- return typeof children === 'function' ? children(validity, value) : children
52
- }
53
- }
56
+ const validity = handleRef.current.validate()
54
57
 
55
- export function ValidatorField(props: Omit<Props, 'registerField' | 'unregisterField' | 'customErrors'>) {
56
- return (
57
- <Context.Consumer>
58
- {(data) => (
59
- <ValidationFieldWrapper
60
- {...props}
61
- customErrors={data?.customErrors}
62
- registerField={data?.registerField}
63
- unregisterField={data?.unregisterField}
64
- />
65
- )}
66
- </Context.Consumer>
67
- )
68
- }
58
+ return typeof children === 'function' ? (children as Fn)(validity, value) : (children as ReactNode)
59
+ })
@@ -2,27 +2,23 @@
2
2
  * @jest-environment jsdom
3
3
  */
4
4
 
5
- import { createRef } from 'react'
6
5
  import { render } from '@testing-library/react'
6
+ import { createRef } from 'react'
7
7
 
8
- import { ValidatorWrapper } from './validator-wrapper'
9
- import { ValidatorField } from './validator-field'
10
8
  import { rules } from './rules'
11
-
12
- it('render without child', () => {
13
- expect(() => shallow(<ValidatorWrapper />)).toThrowError()
14
- })
9
+ import { ValidatorField } from './validator-field'
10
+ import { ValidatorWrapper } from './validator-wrapper'
15
11
 
16
12
  it('check wrapper validator', () => {
17
13
  const validator = createRef<ValidatorWrapper>()
18
14
  render(
19
15
  <ValidatorWrapper ref={validator}>
20
- <ValidatorField rules={[]} />
21
- <ValidatorField rules={[]} />
22
- <ValidatorField rules={[]} />
23
- <ValidatorField rules={[]} />
24
- <ValidatorField rules={[]} />
25
- <ValidatorField rules={[]} />
16
+ <ValidatorField rules={[]} value="" />
17
+ <ValidatorField rules={[]} value="" />
18
+ <ValidatorField rules={[]} value="" />
19
+ <ValidatorField rules={[]} value="" />
20
+ <ValidatorField rules={[]} value="" />
21
+ <ValidatorField rules={[]} value="" />
26
22
  </ValidatorWrapper>,
27
23
  )
28
24
 
@@ -34,8 +30,8 @@ it('check getField validator', () => {
34
30
  const validator = createRef<ValidatorWrapper>()
35
31
  render(
36
32
  <ValidatorWrapper ref={validator}>
37
- <ValidatorField rules={[]} id='test' />
38
- <ValidatorField rules={[]} id='test-fields' />
33
+ <ValidatorField rules={[]} id="test" value="" />
34
+ <ValidatorField rules={[]} id="test-fields" value="" />
39
35
  </ValidatorWrapper>,
40
36
  )
41
37
  expect(typeof validator.current.getField).toBe('function')
@@ -52,7 +48,7 @@ it('check getField undefined field', () => {
52
48
  const validator = createRef<ValidatorWrapper>()
53
49
  render(
54
50
  <ValidatorWrapper ref={validator}>
55
- <ValidatorField rules={[]} id='test-empty-field' />
51
+ <ValidatorField rules={[]} id="test-empty-field" value="" />
56
52
  </ValidatorWrapper>,
57
53
  )
58
54
 
@@ -64,9 +60,9 @@ it('check stopAtFirstError validator', () => {
64
60
  const validator = createRef<ValidatorWrapper>()
65
61
  render(
66
62
  <ValidatorWrapper ref={validator} stopAtFirstError>
67
- <ValidatorField rules={[]} value='test' />
68
- <ValidatorField rules={rules.email} value='test' />
69
- <ValidatorField rules={rules.password} value='' />
63
+ <ValidatorField rules={[]} value="test" />
64
+ <ValidatorField rules={rules.email} value="test" />
65
+ <ValidatorField rules={rules.password} value="" />
70
66
  </ValidatorWrapper>,
71
67
  )
72
68
  const fieldValidate = validator.current.validate()
@@ -79,7 +75,7 @@ it('check unregisterField, registerField', () => {
79
75
  const validator = createRef<ValidatorWrapper>()
80
76
  render(
81
77
  <ValidatorWrapper ref={validator}>
82
- <ValidatorField rules={[]} id='test-register-field' />
78
+ <ValidatorField rules={[]} id="test-register-field" value="" />
83
79
  </ValidatorWrapper>,
84
80
  )
85
81
 
@@ -91,9 +87,9 @@ it('check filed in field', () => {
91
87
  const validator = createRef<ValidatorWrapper>()
92
88
  render(
93
89
  <ValidatorWrapper ref={validator}>
94
- <ValidatorField rules={[]}>
95
- <ValidatorField rules={[]} id='check-validate-field-1' />
96
- <ValidatorField rules={[]} id='check-validate-field-2' />
90
+ <ValidatorField rules={[]} value="">
91
+ <ValidatorField rules={[]} id="check-validate-field-1" value="" />
92
+ <ValidatorField rules={[]} id="check-validate-field-2" value="" />
97
93
  </ValidatorField>
98
94
  </ValidatorWrapper>,
99
95
  )
@@ -109,9 +105,9 @@ it('check wrapper in wrapper', () => {
109
105
  const validatorIn = createRef<ValidatorWrapper>()
110
106
  render(
111
107
  <ValidatorWrapper ref={validatorOut}>
112
- <ValidatorField rules={rules.email} value='' />
108
+ <ValidatorField rules={rules.email} value="" />
113
109
  <ValidatorWrapper ref={validatorIn}>
114
- <ValidatorField rules={rules.password} value='successpasswword' />
110
+ <ValidatorField rules={rules.password} value="successpasswword" />
115
111
  </ValidatorWrapper>
116
112
  </ValidatorWrapper>,
117
113
  )
@@ -125,10 +121,10 @@ it('check two validators', () => {
125
121
  render(
126
122
  <>
127
123
  <ValidatorWrapper ref={validatorSuccess}>
128
- <ValidatorField rules={rules.password} value='successpasswword' />
124
+ <ValidatorField rules={rules.password} value="successpasswword" />
129
125
  </ValidatorWrapper>
130
126
  <ValidatorWrapper ref={validatorFailed}>
131
- <ValidatorField rules={rules.email} value='' />
127
+ <ValidatorField rules={rules.email} value="" />
132
128
  </ValidatorWrapper>
133
129
  </>,
134
130
  )
@@ -136,3 +132,21 @@ it('check two validators', () => {
136
132
  expect(validatorFailed.current.validate().isValid).toBe(false)
137
133
  expect(validatorSuccess.current.validate().isValid).toBe(true)
138
134
  })
135
+
136
+ it('covers registerField duplicate and unregisterField non-existing branches', () => {
137
+ const validator = createRef<ValidatorWrapper>()
138
+ render(
139
+ <ValidatorWrapper ref={validator}>
140
+ <ValidatorField rules={[]} id="dup-field" value="" />
141
+ </ValidatorWrapper>,
142
+ )
143
+
144
+ const handle = validator.current.getField('dup-field')
145
+ validator.current.registerField(handle)
146
+ validator.current.unregisterField(handle)
147
+ const dummy = {
148
+ props: { value: '', rules: [], id: 'dummy' },
149
+ validate: () => ({ isValid: true, message: '' }),
150
+ }
151
+ validator.current.unregisterField(dummy)
152
+ })
@@ -1,75 +1,78 @@
1
- import { Component, ReactNode, RefObject } from 'react'
1
+ import { forwardRef, type ReactNode, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'
2
2
 
3
- import { Validity } from './types'
4
- import { Context } from './context'
5
- import { Field, Validator } from './validator'
3
+ import { Context, type RegisteredFieldHandle } from './context'
4
+ import type { Validity } from './types'
5
+ import { Validator } from './validator'
6
6
 
7
7
  interface ComponentProps {
8
8
  children?: ReactNode
9
9
  stopAtFirstError?: boolean
10
- ref?: RefObject<any>
11
10
  }
12
11
 
13
- export class ValidatorWrapper extends Component<ComponentProps> {
14
- fields = []
15
- state = {
16
- customErrors: [],
17
- }
18
-
19
- constructor(props, ctx) {
20
- super(props, ctx)
21
- this.registerField = this.registerField.bind(this)
22
- this.unregisterField = this.unregisterField.bind(this)
23
- }
12
+ export interface ValidatorWrapper {
13
+ validate: () => Validity
14
+ getField: (id: string | number) => RegisteredFieldHandle | null
15
+ registerField: (field: RegisteredFieldHandle) => void
16
+ unregisterField: (field: RegisteredFieldHandle) => void
17
+ setCustomError: (customError: Validity) => void
18
+ clearCustomErrors: () => void
19
+ }
24
20
 
25
- componentWillUnmount() {
26
- this.fields = []
27
- }
21
+ export const ValidatorWrapper = forwardRef<ValidatorWrapper, ComponentProps>(function ValidatorWrapper(
22
+ { children, stopAtFirstError },
23
+ ref,
24
+ ) {
25
+ const fieldsRef = useRef<RegisteredFieldHandle[]>([])
26
+ const [customErrors, setCustomErrors] = useState<Validity[]>([])
28
27
 
29
- registerField(field) {
30
- if (field && !this.fields.includes(field)) {
31
- this.fields.push(field)
28
+ const registerField = useCallback((field: RegisteredFieldHandle) => {
29
+ if (field && !fieldsRef.current.includes(field)) {
30
+ fieldsRef.current.push(field)
32
31
  }
33
- }
32
+ }, [])
34
33
 
35
- unregisterField(field) {
36
- const index = this.fields.indexOf(field)
37
- if (index > -1) this.fields.splice(index, 1)
38
- }
34
+ const unregisterField = useCallback((field: RegisteredFieldHandle) => {
35
+ const index = fieldsRef.current.indexOf(field)
36
+ if (index > -1) fieldsRef.current.splice(index, 1)
37
+ }, [])
39
38
 
40
- getField(id): Field | null {
41
- return this.fields.find((field) => field.props.id === id) || null
42
- }
39
+ const getField = useCallback<ValidatorWrapper['getField']>((id) => {
40
+ return fieldsRef.current.find((field) => field?.props?.id === id) || null
41
+ }, [])
43
42
 
44
- setCustomError(customError: Validity) {
45
- this.setState({
46
- customErrors: [...this.state.customErrors, customError],
47
- })
48
- }
43
+ const setCustomError = useCallback<ValidatorWrapper['setCustomError']>((customError) => {
44
+ setCustomErrors((prev) => [...prev, customError])
45
+ }, [])
49
46
 
50
- clearCustomErrors() {
51
- this.setState({ customErrors: [] })
52
- }
47
+ const clearCustomErrors = useCallback<ValidatorWrapper['clearCustomErrors']>(() => {
48
+ setCustomErrors([])
49
+ }, [])
53
50
 
54
- validate(): Validity {
55
- const validator = new Validator({ stopAtFirstError: this.props.stopAtFirstError })
56
- this.fields.forEach((comp) => {
51
+ const validate = useCallback<ValidatorWrapper['validate']>(() => {
52
+ const validator = new Validator({ stopAtFirstError })
53
+ for (const comp of fieldsRef.current) {
57
54
  validator.addField(comp.props)
58
- })
55
+ }
59
56
  return validator.validate()
60
- }
57
+ }, [stopAtFirstError])
61
58
 
62
- render() {
63
- return (
64
- <Context.Provider
65
- value={{
66
- customErrors: this.state.customErrors,
67
- registerField: this.registerField,
68
- unregisterField: this.unregisterField,
69
- }}
70
- >
71
- {this.props.children}
72
- </Context.Provider>
73
- )
74
- }
75
- }
59
+ useImperativeHandle(
60
+ ref,
61
+ () => ({
62
+ validate,
63
+ getField,
64
+ registerField,
65
+ unregisterField,
66
+ setCustomError,
67
+ clearCustomErrors,
68
+ }),
69
+ [validate, getField, registerField, unregisterField, setCustomError, clearCustomErrors],
70
+ )
71
+
72
+ const contextValue = useMemo(
73
+ () => ({ customErrors, registerField, unregisterField }),
74
+ [customErrors, registerField, unregisterField],
75
+ )
76
+
77
+ return <Context.Provider value={contextValue}>{children}</Context.Provider>
78
+ })
@@ -28,3 +28,13 @@ it('check normal add and remove fields', () => {
28
28
  newFieldSearchPassword = validator.getField('for-remove')
29
29
  expect(newFieldSearchPassword === null).toBe(true)
30
30
  })
31
+
32
+ it('removeField does nothing when field is not registered', () => {
33
+ const validator = new Validator()
34
+ // create a field-like object that validator doesn't know about
35
+ // import Field from the module is possible, but we can emulate shape using any
36
+ // Should not throw and should not alter state
37
+ // @ts-expect-error.
38
+ expect(() => validator.removeField({})).not.toThrow()
39
+ expect(validator.getField('unknown')).toBe(null)
40
+ })
package/src/validator.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { Value } from './validator-field'
2
- import { ValidatorRules } from './rules'
3
- import { FieldParams, Validity } from './types'
1
+ import type { ValidatorRules } from './rules'
2
+ import type { FieldParams, Validity } from './types'
3
+ import type { Value } from './validator-field'
4
4
 
5
5
  export class Field {
6
- private rules: ValidatorRules
7
- private required: boolean
8
- private value: Value
6
+ protected rules: ValidatorRules
7
+ protected required: boolean
8
+ protected value: Value
9
9
  public id: string | number
10
10
 
11
11
  constructor({ rules, required, value, id }: FieldParams) {
@@ -20,12 +20,12 @@ export class Field {
20
20
  let message = ''
21
21
  const { rules, value, required, id } = this
22
22
 
23
- const isEmptyValue = !value && parseFloat(value) !== 0
23
+ const isEmptyValue = !value && Number.parseFloat(value) !== 0
24
24
 
25
25
  if (!rules.length || (isEmptyValue && required === false)) {
26
26
  return { isValid, message, id }
27
27
  }
28
- rules.forEach((instance) => {
28
+ for (const instance of rules) {
29
29
  if (isValid) {
30
30
  isValid = instance.rule(value)
31
31
  if (!isValid) {
@@ -36,7 +36,7 @@ export class Field {
36
36
  }
37
37
  }
38
38
  }
39
- })
39
+ }
40
40
  return { isValid, message, id }
41
41
  }
42
42
  }
@@ -70,7 +70,7 @@ export class Validator {
70
70
  }
71
71
 
72
72
  validate(): Validity {
73
- let prevResult
73
+ let prevResult: Validity | null
74
74
  const statuses = this.fields.map((field) => {
75
75
  if (this.params?.stopAtFirstError && prevResult && prevResult.isValid === false) {
76
76
  return null
package/tsconfig.json CHANGED
@@ -6,21 +6,17 @@
6
6
  "removeComments": true,
7
7
  "emitDecoratorMetadata": true,
8
8
  "experimentalDecorators": true,
9
- "target": "es2017",
9
+ "target": "ESNext",
10
10
  "sourceMap": false,
11
- "composite": true,
12
- "incremental": true,
11
+ "composite": false,
12
+ "incremental": false,
13
13
  "noUnusedLocals": true,
14
14
  "noUnusedParameters": false,
15
15
  "resolveJsonModule": true,
16
16
  "outDir": "./dist",
17
17
  "rootDir": "src",
18
18
  "baseUrl": "./src",
19
- "lib": [
20
- "dom",
21
- "dom.iterable",
22
- "esnext"
23
- ],
19
+ "lib": ["dom", "dom.iterable", "esnext"],
24
20
  "allowJs": true,
25
21
  "skipLibCheck": true,
26
22
  "strict": false,
@@ -28,12 +24,6 @@
28
24
  "moduleResolution": "node",
29
25
  "jsx": "react-jsx"
30
26
  },
31
- "include": [
32
- "src",
33
- "src/**/*"
34
- ],
35
- "exclude": [
36
- "node_modules",
37
- "dist"
38
- ]
27
+ "include": ["src", "src/**/*"],
28
+ "exclude": ["node_modules", "dist"]
39
29
  }