Skip to content

Commit f6cdcaf

Browse files
apanizoerikras
authored andcommitted
feat(useField) Add validation at field's level (#35)
* Add JEST * Add validate function * Add test checking field validation works * Adding types to useField form
1 parent 95a03c6 commit f6cdcaf

File tree

5 files changed

+122
-21
lines changed

5 files changed

+122
-21
lines changed

package-lock.json

Lines changed: 30 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"doctoc": "doctoc README.md && doctoc docs/faq.md && prettier --write README.md && prettier --write docs/faq.md",
1717
"precommit": "lint-staged && npm start validate",
1818
"prepublish": "npm start validate",
19-
"postinstall": "node ./scripts/postinstall.js || exit 0"
19+
"postinstall": "node ./scripts/postinstall.js || exit 0",
20+
"test": "NODE_ENV=test jest -i --detectOpenHandles"
2021
},
2122
"author": "Erik Rasmussen <rasmussenerik@gmail.com> (http://github.com/erikras)",
2223
"license": "MIT",
@@ -60,7 +61,7 @@
6061
"final-form": "^4.11.1",
6162
"hoek": ">=4.2.1",
6263
"husky": "^1.1.1",
63-
"jest": "^24.1.0",
64+
"jest": "^24.5.0",
6465
"lint-staged": "^8.1.3",
6566
"merge": ">=1.2.1",
6667
"nps": "^5.9.3",

src/index.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
FormState,
55
FormSubscription,
66
FieldSubscription,
7-
FieldState
7+
FieldState,
8+
FieldValidator
89
} from 'final-form'
910

1011
export interface FormRenderProps extends FormState {
@@ -46,6 +47,7 @@ declare module 'react-final-form-hooks' {
4647
export function useField<V = any>(
4748
name: string,
4849
form: FormApi,
50+
validate?: FieldValidator,
4951
subscription?: FieldSubscription
5052
): FieldRenderProps<V>
5153
}

src/useField.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ const eventValue = event => {
1919
return event.target.value
2020
}
2121

22-
const useField = (name, form, subscription = all) => {
22+
const useField = (name, form, validate, subscription = all) => {
2323
const autoFocus = useRef(false)
2424
const [state, setState] = useState({})
25+
const options = validate
26+
? {
27+
getValidator: () => validate
28+
}
29+
: undefined
2530
useEffect(
2631
() =>
2732
form.registerField(
@@ -33,9 +38,10 @@ const useField = (name, form, subscription = all) => {
3338
}
3439
setState(newState)
3540
},
36-
subscription
41+
subscription,
42+
options
3743
),
38-
[name, form, ...subscriptionToInputs(subscription)]
44+
[name, form, validate, ...subscriptionToInputs(subscription)]
3945
)
4046
let { blur, change, focus, value, ...meta } = state || {}
4147
delete meta.name // it's in input

src/useField.test.js

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
import * as React from 'react'
2+
import ReactDOM from 'react-dom'
3+
import { act } from 'react-dom/test-utils'
14
import { renderHook, cleanup } from 'react-hooks-testing-library'
25
import useField, { all } from './useField'
6+
import useForm from './useForm'
37

48
describe('useField()', () => {
5-
let form, name, subscription
9+
let form, name, subscription, container
610

711
beforeEach(() => {
812
name = 'foo'
913
subscription = { value: true }
14+
container = document.createElement('div')
15+
})
16+
afterEach(() => {
17+
document.body.removeChild(container)
18+
container = null
19+
cleanup()
1020
})
11-
afterEach(cleanup)
1221

1322
describe("form hook parameter's registerField", () => {
1423
beforeEach(() => {
@@ -51,7 +60,9 @@ describe('useField()', () => {
5160
beforeEach(() => {
5261
value = 'bar'
5362
blur = jest.fn()
54-
change = jest.fn()
63+
change = jest.fn(() => {
64+
debugger
65+
})
5566
focus = jest.fn()
5667

5768
form = {
@@ -109,7 +120,7 @@ describe('useField()', () => {
109120
})
110121

111122
describe('when event has a checked prop', () => {
112-
const event = { target: { type: 'radio', checked: false } }
123+
const event = { target: { type: 'checkbox', checked: false } }
113124

114125
it('calls provided handler with value', () => {
115126
const { onChange } = setupHook()
@@ -154,4 +165,66 @@ describe('useField()', () => {
154165
expect(returnMeta).toEqual(meta)
155166
})
156167
})
168+
169+
describe('field level validation', () => {
170+
it('should not call form validation if field validationallow validate field', () => {
171+
const FIELD_NAME = 'firstName'
172+
173+
const onSubmit = jest.fn()
174+
const validate = jest.fn(values => {
175+
let errors = {}
176+
if (values[FIELD_NAME] && values[FIELD_NAME].length <= 4) {
177+
errors[FIELD_NAME] = 'Must be at least 4 chars'
178+
}
179+
return errors
180+
})
181+
182+
const required = jest.fn(value => (value ? undefined : 'Required'))
183+
184+
const FormComponent = () => {
185+
const { form, handleSubmit } = useForm({
186+
onSubmit,
187+
validate
188+
})
189+
const firstName = useField(FIELD_NAME, form, required)
190+
191+
return (
192+
<form onSubmit={handleSubmit}>
193+
<label>First Name</label>
194+
<input {...firstName.input} placeholder="First Name" />
195+
{firstName.meta.touched && firstName.meta.error && (
196+
<span>{firstName.meta.error}</span>
197+
)}
198+
<button type="submit">Submit</button>
199+
</form>
200+
)
201+
}
202+
203+
act(() => {
204+
ReactDOM.render(<FormComponent />, container)
205+
})
206+
207+
expect(validate).toHaveBeenCalledTimes(2)
208+
expect(required).toHaveBeenCalledTimes(1)
209+
210+
// span is not in dom because field error is not raised
211+
expect(container.querySelector('span')).toBe(null)
212+
213+
// submit
214+
const button = container.querySelector('button')
215+
act(() => {
216+
button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
217+
})
218+
219+
// span has required error
220+
expect(container.querySelector('span').innerHTML).toBe('Required')
221+
// form validate function has not been called
222+
expect(validate).toHaveBeenCalledTimes(2)
223+
// onSubmit has not been called in any moment
224+
expect(onSubmit).not.toHaveBeenCalled()
225+
226+
// why this is not updated again?
227+
expect(required).toHaveBeenCalledTimes(1)
228+
})
229+
})
157230
})

0 commit comments

Comments
 (0)