react-form-state
v0.3.0
Published
It's all about you already know about controlling the state inside a component, but made easier to write expressive data-driven forms.
Downloads
54
Readme
Form-State
A straightforward form management
It's all about you already know about controlling the state inside a component, but made easier to write expressive data-driven forms.
You will not have to learn a new way of doing things as many libraries out there does today. Form-State can be seen as just as a small package that helps you with forms without extra overheading components.
Built on top of some lodash/fp functions, this library provides immutability out of the box with.
Background
This library is built with these principles in mind:
- Keep data immutable
- Avoid extra components, render functions or new syntax
- Configuration over code
- setState only when needed
- Keep track on form errors
- Only send the changes made
- DRY
That being said and in order to keep a straightforward form component, the first thing we do is define the schema that the form is based on (configuration over code). To achieve that, we use Joi to write it down, but also to normalize and validate the data. It's a very powerful library with a clean syntax that makes you understand all of your form should provide in a breeze. [https://github.com/jeffbski/joi-browser]
Installation
yarn add react-form-state
Usage
import Form from 'react-form-state';
Coupling the form state inside a component state is just as easy as defining three objects in it.
state = {
form: {},
changes: {},
errors: {}
};
See? No magic. You can also use a shortcut
state = {
...Form.defaultState
};
The form
object keeps the raw user input. changes
holds all the data that was modified and is valid! errors
is, well, the errors. Next, provide a schema that your form is based on.
schema = {
firstName: Joi.string().min(5).required(),
email: Joi.string().email().required(),
age: Joi.number().min(0).max(100).required()
}
class ProfileForm extends React.Component {
constructor(props) {
super(props);
this.form = new Form({ schema });
this.state = { ...Form.defaultState };
}
}
Form-State manages a small state inside of it. Its changes reflects back to the component state. Thus, let's provide a hook that make those changes happen. It's not required though, all form methods returns a new state object that you can manually merge into yours component state, if you want to. You are in charge.
class ProfileForm extends React.Component {
constructor(props) {
super(props);
this.form = new Form({ schema, onChange: this.onFormChange });
this.state = { ...Form.defaultState };
}
onFormChange = (formState) => this.setState({ ...formState });
}
formState
wraps form
, changes
and errors
as we defined before. A shallow merge into the component state is just fine, as Form-State already handle nested changes and creates new objects everytime they have been modified, guaranteeing immutability.
Finally, let's bring the form and put it all togheter.
class ProfileForm extends React.Component {
constructor(props) {
super(props);
this.form = new Form({ schema, onChange: this.onFormChange });
this.state = { ...Form.defaultState };
}
onFormChange = (formState) => this.setState({ ...formState });
render() {
const { errors, form: profile } = this.state;
const canSubmit = this.form.hasChanges() && !this.form.hasErrors();
return (
<div>
<label for="firstName">First Name {errors.firstName}</label>
<input name="firstName" value={profile.firstName}
onChange={this.form.handleChange} />
<label for="email">Email {errors.firstName}</label>
<input name="email" value={profile.email}
onChange={this.form.handleChange} />
<label for="age">Email {errors.age}</label>
<input name="age" value={profile.age}
onChange={this.form.handleChange} />
{canSubmit && <button onClick={this.handleSubmit}>Submit!</button>}
</div>
)
}
}
Your handleSubmit function can be simple as just grabbing the changes
object and dispatching it to the server. For example, if you are using redux:
handleSubmit = () => {
const { changes } = this.state;
this.props.dispatch(saveProfile(changes));
}
Peforming setState manually on form changes
Form-State holds a brief state so every time a field changes, it can perform all tasks involved to validate the data, merge the errors, raw and the normalized input to, finally call the trigger that will eventually call setState in the component. This is done all by once, so the setState will be called only once also, and you won't have to write waterfall setState's for every possible change.
On the other hand, you can easily get to the bare metal here.
handeChange = (event) => {
const { name, value } = event.target;
const newState = this.form.set(name, value);
this.setState({ ...newState });
}
As set
peforms triggers the input validation, you can also merge the data you want, if you need bypass any validation.
checkName = (name) => {
if (name === 'john') {
const errors = { firstName: 'already exists!' };
const newState = this.form.merge({ errors });
this.setState({ ...newState });
}
}
Or even update (replace) a whole form state. Be in mind that with great powers comes great resposabilities.
Common pitfalls
Keep in mind that every time you manually peform a changing in the form state that you want both, the user see the changes feedback and set these changes to be commited when submitting the form, you need to merge/update form
and changes
sub states.
If you are adding extra data in the form state that is not defined in the schema, this library won't track its errors and so will not clear them by itself, if you are setting any. You will have to manage it too.
Nevertheless, you don't need to be worry about using the onFormChange trigger and managing some extra data by yourself. react-form-state
will only clear errors for fields that are defined on the schema.