Skip to main content
Version: 6.x (next)

Handling User Interaction

A common challenge in form validation is "noise control." You don't want to scream errors at a user before they've even touched a field.

Traditionally, libraries use an isDirty flag to track if a user has modified a field. Since Vest is UI-agnostic (it doesn't touch your DOM or listen to events), it doesn't track "dirty" state for you.

Instead, Vest provides two powerful tools to handle user interaction: isTested() and suite.focus().

1. isTested(): The Vest Alternative to isDirty​

When you want to decide if you should show an error message, you usually want to know: "Has this field actually been validated yet?"

If a field hasn't been validated, it usually means the user hasn't interacted with it. Vest tracks this for you.

const result = suite.get();

// Only show errors if the field has actually been tested
const shouldShowError =
result.hasErrors('username') && result.isTested('username');

if (shouldShowError) {
renderError(result.getErrors('username'));
}

This pattern ensures that empty, untouched fields don't show "Required" errors when the form first loads.

2. Validating on Interaction with suite.focus()​

When a user blurs a field or types, you often want to validate only that specific field, while keeping the rest of the form state intact.

Vest 6 introduces suite.focus(). This tells Vest to run validations for specific fields, while skipping others.

// On Blur handler
function handleBlur(fieldName, formData) {
// 1. Tell Vest to focus ONLY on the blurred field
// 2. Run the suite with the current data
suite.focus({ only: fieldName }).run(formData);
}

Why use focus()?​

  • Performance: It skips expensive tests (like async checks) for fields the user isn't touching.
  • User Experience: It updates the state for the current field without accidentally flagging other fields as "tested" or "invalid" before the user reaches them.
Real-World Pattern

Combine suite.focus() with isTested() for the best UX:

  • Use focus({ only: fieldName }) in your onBlur handler to validate only the current field
  • Use isTested(fieldName) when rendering to decide whether to show errors

Complete Example​

import suite from './validation';

function Form() {
const [formData, setFormData] = useState({});
const [result, setResult] = useState(suite.get());

const handleChange = e => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};

const handleBlur = e => {
const { name } = e.target;
// Validate only the blurred field
const res = suite.focus({ only: name }).run(formData);
setResult(res);
};

const handleSubmit = e => {
e.preventDefault();
// Validate all fields on submit
const res = suite.run(formData);
setResult(res);

if (res.isValid()) {
// Submit the form
}
};

// Only show error if field was tested
const showError = fieldName => {
return result.isTested(fieldName) && result.hasErrors(fieldName);
};

return (
<form onSubmit={handleSubmit}>
<input name="username" onChange={handleChange} onBlur={handleBlur} />
{showError('username') && <span>{result.getError('username')}</span>}

<button type="submit">Submit</button>
</form>
);
}

Summary​

GoalTraditional ApproachVest Approach
Did the user touch this?Check field.isDirtyCheck result.isTested('field')
Validate on BlurCall validateField('field')Call suite.focus({ only: 'field' }).run(data)

By combining isTested() (to hide premature errors) and suite.focus() (to update specific fields), you get precise control over the user experience without tightly coupling your validation to the DOM.