Skip to content
This repository was archived by the owner on Feb 29, 2024. It is now read-only.

Commit 7aa4da9

Browse files
committed
validating input only after an specific amount of time, for better usability and performance .. then automatically touch it so the errors get displayed
1 parent 5677593 commit 7aa4da9

File tree

6 files changed

+41
-33
lines changed

6 files changed

+41
-33
lines changed

README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import React from 'react';
3434
import Input from '@cat-react/form/Input'
3535

3636
@Input
37-
export default class TextInput extends React.Component {
37+
export default class BasicInput extends React.Component {
3838
onChange(event) {
3939
this.props.setValue(event.target.value);
4040
}
@@ -49,26 +49,28 @@ export default class TextInput extends React.Component {
4949
return null;
5050
}
5151

52-
return <ul>{errorMessages.map((message, i) => <li key={i}>{message}</li>)}</ul>;
52+
return <ul className="errorText">{errorMessages.map((message, i) => <li key={i}>{message}</li>)}</ul>;
5353
}
5454

5555
render() {
56-
let className = '';
56+
let className = 'form-control';
5757
if (!this.props.isPristine()) {
58-
className = this.props.isValid() ? null : 'error';
58+
className += this.props.isValid() ? '' : ' error';
5959
}
6060

61-
// TODO: remove onBlur
6261
return (
63-
<label>
64-
{this.props.label} {this.props.isRequired() ? '*' : null}
65-
<input className={className}
66-
type="text"
62+
<div className="form-group">
63+
<label htmlFor={this.props.name}>{this.props.label} {this.props.isRequired() ? '*' : null}</label>
64+
<input type={this.props.type}
65+
className={className}
66+
id={this.props.name}
67+
aria-describedby={this.props.name}
68+
placeholder={this.props.placeholder}
6769
value={this.props.getValue()}
6870
onChange={this.onChange.bind(this)}
69-
onBlur={this.props.onBlur}/>
71+
onBlur={this.props.touch} />
7072
{this.renderErrors()}
71-
</label>
73+
</div>
7274
);
7375
}
7476
}

examples/Login/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export default class App extends React.Component {
2727
});
2828
}
2929

30+
onSubmit(values, valid) {
31+
console.log(values, valid);
32+
}
33+
3034
onValidSubmit(values) {
3135
this.setState({
3236
values: values
@@ -37,6 +41,7 @@ export default class App extends React.Component {
3741
return (
3842
<Form onValid={this.onValid}
3943
onInvalid={this.onInvalid}
44+
onSubmit={this.onSubmit}
4045
onValidSubmit={this.onValidSubmit}>
4146
<h1>Login</h1>
4247
<BasicInput label="Email address"

examples/components/BasicInput.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export default class BasicInput extends React.Component {
2626
className += this.props.isValid() ? '' : ' error';
2727
}
2828

29-
// TODO: remove onBlur
3029
return (
3130
<div className="form-group">
3231
<label htmlFor={this.props.name}>{this.props.label} {this.props.isRequired() ? '*' : null}</label>
@@ -37,7 +36,7 @@ export default class BasicInput extends React.Component {
3736
placeholder={this.props.placeholder}
3837
value={this.props.getValue()}
3938
onChange={this.onChange.bind(this)}
40-
onBlur={this.props.onBlur} />
39+
onBlur={this.props.touch} />
4140
{this.renderErrors()}
4241
</div>
4342
);

src/Form.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ export default class Form extends React.Component {
2525
getChildContext() {
2626
return {
2727
_reactForm: {
28-
enableTouchedOnChange: this.props.enableTouchedOnChange,
2928
attach: this.attachInput,
3029
detach: this.detachInput,
31-
validate: this.validateInput,
30+
addToValidationQueue: this.addToValidationQueue,
31+
startValidation: this.startValidation,
3232
getValues: this.getValues
3333
}
3434
};
@@ -200,11 +200,7 @@ Form.propTypes = {
200200
onValidSubmit: PropTypes.func,
201201
onInvalidSubmit: PropTypes.func,
202202
onValid: PropTypes.func,
203-
onInvalid: PropTypes.func,
204-
enableTouchedOnChange: PropTypes.bool
205-
};
206-
Form.defaultProps = {
207-
enableTouchedOnChange: false
203+
onInvalid: PropTypes.func
208204
};
209205
Form.childContextTypes = {
210206
_reactForm: PropTypes.object

src/Input.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import PropTypes from 'prop-types';
33
import autoBind from 'auto-bind';
44
import Form from './Form';
55

6+
const CHANGE_VALUE_TIMEOUT = 350;
7+
68
export default function (WrappedComponent) {
79
class Input extends React.Component {
810
constructor(props, context) {
911
super(props, context);
1012

13+
this.changeValueTimer = null;
1114
this.dependencies = [];
1215
for (let dependency of props.dependencies) {
1316
this.addDependency(dependency);
@@ -79,18 +82,15 @@ export default function (WrappedComponent) {
7982
}
8083

8184
setValue(value) {
82-
// TODO: check if needed, maybe instead just wait 500ms for validation (and cancel if value gets changed before)
83-
const isTouchedOnChange = this.context._reactForm.enableTouchedOnChange;
84-
if (isTouchedOnChange && this.isPristine()) {
85-
this.setState({
86-
pristine: false
87-
});
88-
}
89-
85+
clearTimeout(this.changeValueTimer);
86+
this.context._reactForm.addToValidationQueue(this);
9087
this.setState({
9188
value: value
9289
}, () => {
93-
this.context._reactForm.validate(this);
90+
this.changeValueTimer = setTimeout(() => {
91+
this.touch();
92+
this.context._reactForm.startValidation();
93+
}, CHANGE_VALUE_TIMEOUT);
9494
});
9595
}
9696

@@ -160,7 +160,7 @@ export default function (WrappedComponent) {
160160
getValue: this.getValue,
161161
setValue: this.setValue,
162162
getErrorMessages: this.getErrorMessages,
163-
onBlur: this.touch,
163+
touch: this.touch,
164164
...this.props
165165
};
166166

tests/Input.spec.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ let formContext = {
1212
_reactForm: {
1313
attach: jest.fn(),
1414
detach: jest.fn(),
15-
validate: jest.fn(),
15+
addToValidationQueue: jest.fn(),
16+
startValidation: jest.fn(),
1617
getValues: function () {
1718
return mockValues;
1819
}
@@ -50,12 +51,17 @@ describe('Input', () => {
5051
expect(instance.isPristine()).toBe(false);
5152
});
5253

53-
it('should change the value correctly', () => {
54+
it('should change the value correctly', (done) => {
5455
let wrapper = shallow(<CustomInput name="email" value="myValue2"/>, formContext);
5556
wrapper.instance().setValue('myValue3');
5657
wrapper.update();
5758
expect(wrapper.instance().getValue()).toBe('myValue3');
58-
expect(formContext.context._reactForm.validate).toBeCalledWith(wrapper.instance());
59+
expect(formContext.context._reactForm.addToValidationQueue).toBeCalledWith(wrapper.instance());
60+
expect(formContext.context._reactForm.startValidation).toHaveBeenCalledTimes(0);
61+
setTimeout(function () {
62+
expect(formContext.context._reactForm.startValidation).toHaveBeenCalledTimes(1);
63+
done();
64+
}, 400);
5965
});
6066

6167
it('should unmount correctly', () => {

0 commit comments

Comments
 (0)