React Form Hook, so young, so simple.
$ npm i -S @pickjunk/use-form
import React from 'react';
import useForm from '@pickjunk/use-form';
export default function Greeting() {
const form = useForm({
who: {
initValue: '',
validators: [v => (!v ? 'who is required!' : '')],
},
});
return (
<div>
<div style={{ marginBottom: 8 }}>hello {form.data('who') || '____'}</div>
{form.field('who', function({
value,
setValue,
validate,
error,
status,
}) {
return (
<>
<div>
<input
value={value}
onChange={function(e) {
const v = e.currentTarget.value;
validate(v);
setValue(v);
}}
/>
</div>
{status === 'fail' && <div style={{ color: 'red' }}>{error}</div>}
</>
);
})}
<button
style={{ marginTop: 8 }}
onClick={async function() {
const valid = await form.validate();
if (valid) {
// submit
}
}}
>
submit
</button>
</div>
);
}
useForm
accept one argument to define fields.
interface Fields {
[field: string]: {
initValue: any;
validators?: ((v: any) => string)[];
};
}
Caution that you must not change the number and the sort of the fields in every rendering within the component. Because useForm
will iterate the fields object and use react hook in the iteration. More details could be found in react hook official document.
Once you have defined a form, feel free to render every field by form.field
decorators. The decorator has two parameters, the first is field name, the second is a rendering callback.
In the rendering callback, things are intuitive. All status and methods about the field will be exported from the first parameter of the callback. They are value
, setValue
, validate
, status
and error
.
Quick Start have demonstrated how things work. No much more to tell. If you want to know more details, typescript hints may help you when typing code.
Get field data by form.data
.
-
If you provide a field name, it will return the single field value:
const who = form.data('who')
. -
If you don't provide any field name, it will return all values of the fields, as a data object:
const { who } = form.data()
.
Besides, remember the data object returned from form.data()
is immutable, whick means it will always be a different reference as long as any of its properties has been changed.
Simply call await form.validate()
when you want to validate all form fields before submitting, as Quick Start shows.
The validators of @pickjunk/use-form support debounce internally:
async function nameValid(name: string) {
await new Promise(function (r) {
setTimeout(r, 2000);
});
return '';
}
nameValid.debounce = 1500;
const form = useForm({
name: {
initValue: '',
validators: [nameValid],
},
});
The most powerful feature of @pickjunk/use-form is the elegant sub form. Look at the following example:
import React from 'react';
import useForm from '@pickjunk/use-form';
function SubForm({
value,
onChange,
}: {
value: string;
onChange: (v: string) => void;
}) {
const form = useForm({
value: {
initValue: '',
validators: [v => (!v ? 'who is required!' : '')],
},
});
form.link('value', { value, onChange });
return form.field('value', function({
value,
setValue,
validate,
error,
status,
}) {
return (
<>
<div>
<input
value={value}
onChange={function(e) {
const v = e.currentTarget.value;
validate(v);
setValue(v);
}}
/>
</div>
{status === 'fail' && <div style={{ color: 'red' }}>{error}</div>}
</>
);
});
}
export default function Greeting() {
const form = useForm({
who: {
initValue: '',
},
});
return (
<div>
<div style={{ marginBottom: 8 }}>hello {form.data('who') || '____'}</div>
{form.field('who', function({ value, setValue }) {
return <SubForm value={value} onChange={setValue} />;
})}
<button
style={{ marginTop: 8 }}
onClick={async function() {
const valid = await form.validate();
if (valid) {
// submit
}
}}
>
submit
</button>
</div>
);
}
Obviously, it is equal to Quick Start. The different point is in the example in Sub Form, the input component is wrapped as a sub component. And in the sub component, a sub form is created.
Noticed that when we talk about sub form, actually it means two points:
-
In one hand, we want the sub form to take charge of its own validators. Besides, when calling
form.validate()
in the parent form, we expect the parent can notify all children to validate themselves. -
In the other hand, we want to collect data from every children as a parent field value.
@pickjunk/use-form concrete these two points so wisely.
For the first point, an internal context will be created by every form.field
decorator. Through these contexts, a tree of the form instances could be maintained. So it's easy to call form.validate
in the parent form to notify all children validating themselves.
For the second point, form.link
is provided to connect the data flow between parent and chilren easily. It has two types:
-
If you provide the field name, it links a single field of the sub form:
form.link('field', { value, onChange })
. -
If you don't provide any field name, it links the whole data object of the sub form:
form.link({ value, onChange })
. Once again to remind, the data object is immutable.