Controlled form
Every field is React state and checked as you type - nothing leaves the browser. Compare it with the form PAD checks.
useStateonSubmit
Live
How it is built
PAD, on the server React, in the browser
<div id="example2"></div>
<script type="text/babel" src="{$padRoot}react/examples/form.js"></script>// A controlled form: each field's value is state, each keystroke an update, and the checks
// run as you type - entirely in the browser. Nothing is sent anywhere; the form of
// "Forms checked by PAD" is the one the server checks.
const formRules = {
name: value => value.trim().length >= 2 || 'At least two characters',
email: value => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value) || 'That is no e-mail address',
plan: value => value !== '' || 'Pick a plan'
};
function FormExample() {
const [values, setValues] = React.useState({ name: '', email: '', plan: '', news: true });
const [touched, setTouched] = React.useState({});
const [sent, setSent] = React.useState(null);
const problems = Object.fromEntries(Object.entries(formRules).map(([name, rule]) => [name, rule(values[name])]).filter(([, ok]) => ok !== true));
const valid = Object.keys(problems).length === 0;
const set = name => event => setValues({ ...values, [name]: event.target.type === 'checkbox' ? event.target.checked : event.target.value });
const leave = name => () => setTouched({ ...touched, [name]: true });
const state = name => touched[name] ? (problems[name] ? ' is-invalid' : ' is-valid') : '';
if (sent)
return (
<div className="empty-state rise">
<span className="big">✅</span>
<strong>Thanks, {sent.name}!</strong>
<p className="muted">{sent.email} · {sent.plan} plan · {sent.news ? 'with' : 'without'} the newsletter</p>
<button className="btn-ghost" onClick={() => { setSent(null); setTouched({}); }}>Fill it in again</button>
</div>
);
return (
<div className="cols-2">
<form noValidate onSubmit={event => { event.preventDefault(); setTouched({ name: true, email: true, plan: true }); if (valid) setSent(values); }}>
<div className="field">
<label htmlFor="form-name">Name</label>
<input id="form-name" className={'input' + state('name')} value={values.name} onChange={set('name')} onBlur={leave('name')} />
{touched.name && problems.name && <span className="error">{problems.name}</span>}
</div>
<div className="field">
<label htmlFor="form-email">E-mail</label>
<input id="form-email" className={'input' + state('email')} value={values.email} onChange={set('email')} onBlur={leave('email')} />
{touched.email && problems.email && <span className="error">{problems.email}</span>}
</div>
<div className="field">
<label htmlFor="form-plan">Plan</label>
<select id="form-plan" className={'select input' + state('plan')} value={values.plan} onChange={set('plan')} onBlur={leave('plan')}>
<option value="">Choose ...</option>
<option value="free">Free</option>
<option value="team">Team</option>
<option value="enterprise">Enterprise</option>
</select>
{touched.plan && problems.plan && <span className="error">{problems.plan}</span>}
</div>
<label className="small" style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 16 }}>
<input type="checkbox" checked={values.news} onChange={set('news')} /> Send me the newsletter
</label>
<button type="submit">Sign up</button>
</form>
<div className="panel">
<div className="panel-title">The state, as you type</div>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>{JSON.stringify(values, null, 2)}</pre>
<div className="panel-title" style={{ marginTop: 14 }}>Valid</div>
<span className={'badge ' + (valid ? 'is-good' : 'is-bad')}>{valid ? 'yes' : Object.keys(problems).join(', ') + ' not yet'}</span>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('example2')).render(<FormExample />);