All of us, React developers - or even javascript developers that already heard about React ecosystem - know a thing: forms in React application are really painful and verbose (like Formik's devs said too).
We have Redux Forms, Formik and many other libraries to make forms happen in React. But the first not convince me... Why? I don't agree in keep form state inside Redux (could we continue friends?). And the second is very powerful... But (nothing matters if there is a "but" after it) by being very powerful, it becomes very verbose or complex for simple forms (and there is a problem with performance in big forms too, subject to another post). And most of these libraries work with controlled components for input fields. It does not always be the better thing to include in your project if you are going to work with big forms and/or need much performance.
What can we do so? Well, because of these points, a lifestyle/company/community called Rocketseat, located in Brazil, makes Unform:
Easy peasy highly scalable ReactJS & React Native forms! π
Overview
Unform is a performance-focused API for creating powerful forms experiences for both React and React Native. Using hooks, you can build lightweight and composable forms based on ultra-extensible components. Integrate with any form library, validate your fields, and have your data out of the box.
Want to test Unform before using it?
ps: not available with React Native Web or Expo Web, use the iOS/Android devices in Expo Snack.
Need help?
Weβre using GitHub Discussions to create conversations around Unform. It is a place for our community to connect with each other around ideas, questions, issues, and suggestions.
Roadmap
If Unform currently doesn't have a certain feature you think it's awesome, be sure to check out the roadmap to see if this is already planned for the future. Otherwise, we recommendβ¦
Let's see it in action!
You can view the source code of this example in https://github.com/italomlp/unform-example
1. What we will need?
- NodeJS and NPM (LTS version?)
- Yarn (this is optional but faster)
- Create React App
- Unform
- Yup (for validation)
- React Datepicker (a component for datepicker and demonstration of third party components integration)
- A browser (I use Google Chrome)
- A text editor (I use VS Code)
- Terminal (I use Oh My Zsh)
2. Init a react app
yarn create react-app form-example
or
npx create-react-app form-example
or another way described in the create-react-app repository
You will see the following result:
Then we can run our project with:
cd form-example/
yarn start
And see the following in browser:
3. Clean src folder and Install dependencies
After creating the app, we will have this folder structure:
First, I'll remove src/App.test.js
, src/App.css
, src/logo.svg
and src/serviceWorker.js
files, that we will not use in this post.
rm src/App.test.js src/App.css src/logo.svg src/serviceWorker.js
We need then to install our dependencies. The yup
is a good choice for validation purposes (and is the recommendation from unform's team). And the react-datepicker
is an awesome component to demonstrate the integration between Unform and third libs.
yarn add @rocketseat/unform yup react-datepicker
So, we can start coding.
4. Make the form
If you look to your browser, see that the app does not compile anymore. To avoid this, we have to change our src/index.js
to:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
And our src/App.js
to:
import React from 'react';
function App() {
return (
<div>
<h1>Contact form</h1>
</div>
);
}
export default App;
The use of Unform is very simple: we import things and use them in our JSX. Let's see.
// ...
import { Input, Form } from "@rocketseat/unform";
function App() {
return (
<div>
<h1>Contact form</h1>
<Form>
<Input name="fullname" label="Full name" />
<Input name="phone" label="Phone" />
<Input name="email" label="Email" />
<button type="submit">Save</button>
</Form>
</div>
);
}
This works well...
But how can we get the data?
Simple: pass a handle submit function to Form component.
// ...
const handleSubmit = data => {
console.log(data);
};
return (
<div>
<h1>Contact form</h1>
<Form onSubmit={handleSubmit}>
{/* ... */}
But, if I want nested fields?
Cool, just use the Scope
component to wrap nested fields. As you can see, I added address fields like this:
import { Input, Form, Scope } from "@rocketseat/unform"; // added Scope component import
// ...
<Form onSubmit={handleSubmit} >
{/* other Input components */}
<Scope path="address">
<Input name="city" label="City" />
<Input name="state" label="State" />
</Scope>
{/* ... */}
</Form>
The Scope
component is used to tell to React: "hey, my children are properties of an object which I represent". And will resut in:
And if I want to populate the form with initial values?
You can. Just pass an object to initialData
prop of Form
component:
// ...
const initialData = {
fullname: "Italo Menezes",
phone: "999999999",
email: "italo@email.com",
address: {
city: "Rio de Janeiro",
state: "Rio de Janeiro"
}
};
// ...
<Form onSubmit={handleSubmit} initialData={initialData}>
{/* ... */}
</Form>
Ok ok. But validation sometimes is a pain. How can I make it with Unform?
For validation, we integrate with Yup
, that is simply an object schema validator. Don't get me wrong when I say "simply". It is very powerful. What I mean is that power is not always the same that complex. So, let's see.
With object schema validations, we can declare rules to object properties, making them strings, with minimum or maximum length, matching a Regex, and so on.
We will validate our form by these rules:
- Full name has to have at least 5 chars. Required.
- The phone number has to be only numeric and have exactly 9 digits. Required.
- The email has to be in an email format (so obvious). Not required.
- The city is not required.
- The state is not required.
So, with Yup, let's create this object schema:
const schema = Yup.object().shape({
fullname: Yup.string()
.min(5, "The FULL name is only this?")
.required("Full name is required."),
phone: Yup.string()
.matches(/^[0-9]{9}$/g, "Is this a phone number?")
.required("Phone is required."),
email: Yup.string().email("Is this an email?"),
address: Yup.object().shape({
city: Yup.string().notRequired(),
state: Yup.string().notRequired()
})
});
And add it to schema
prop in Form
component:
<Form onSubmit={handleSubmit} schema={schema}>
{/* ... */}
</Form>
Stop! Let's add a simple CSS. This project is becoming very ugly!
Replace the code of src/index.css
with:
body {
padding: 10px;
}
input {
display: block;
margin-bottom: 10px;
}
label {
display: block;
}
span {
display: block;
font-size: 10px;
color: red;
margin-bottom: 15px;
}
Coming back to validations...
If you run this now and click on save without values on inputs, you will get this:
Ok. All fine till now. And if I need to use my own input field in the form?
Well, the Rocketseat devs thought about this too, and make a hook to use with other components called useField
.
We will use the react-datepicker
that was added to our project in the beginning. To do this, we need to wrap it and add the useField
like this:
import React, { useEffect, useState, useRef } from "react"; // add the hooks
import { Input, Form, Scope, useField } from "@rocketseat/unform"; // useField hook
import * as Yup from "yup";
import ReactDatepicker from "react-datepicker"; // react datepicker component
import "react-datepicker/dist/react-datepicker.css"; // react datepicker css
// ...
const Datepicker = ({ name, label }) => {
const ref = useRef(null); // for ref manipulation purposes
const { fieldName, registerField, defaultValue, error } = useField(name); // the name of the prop in form object is used here
const [selectedDate, setSelectedDate] = useState(defaultValue); // the state of our datepicker component
useEffect(() => {
registerField({ // here, we're registering the field in the whole form
name: fieldName,
ref: ref.current,
path: "props.selected", // this is the path to selected date in ReactDatepicker (wich is the selected prop)
clearValue: pickerRef => { // for reset purposes
pickerRef.clear();
}
});
}, [fieldName]);
return (
<>
{/* the label is like label in Unform Input component */}
{!!label && <label htmlFor="datepicker">{label}</label>}
<ReactDatepicker
id="datepicker"
name={fieldName}
selected={selectedDate}
onChange={date => setSelectedDate(date)}
ref={ref}
/>
{/* the error is like error in Unform Input component */}
{error && <span>{error}</span>}
</>
);
};
// ...
<Form onSubmit={handleSubmit} schema={schema}>
{/* ... */}
<Datepicker name="birthDate" label="Birth date" />
{/* ... */}
</Form>
Well, I added comments in the code, hope you understand.
If you are not familiar with React hooks, I recommend one reading:
Hooks at a glance: https://reactjs.org/docs/hooks-overview.html
And finally, if I want to reset values after submit?
The onSubmit
function of Form
has a second parameter which is an object. This object has (till the date of this post was wrote) only one property helper, called resetForm
. We can use it like this:
const handleSubmit = (data, { resetForm }) => {
console.log(data);
resetForm();
};
We finished here. You can see more examples and docs in the Unform repository on Github. Unform is in its initial releases, but has an awesome community involved and the best devs I've never meet.
If you like this post, share and give it a β€οΈ. Also, you can follow me on Github and social media. Thanks for reading, and see you later!
Top comments (9)
Finally, a React Form package that isn't stupidly complex for no reason. I've been toying with the idea lately to utilize React in some of our larger apps that currently use Vue, as it's concepts of immutability would help reason about our complex data flow a bit better... But forms are a nightmare in React and, since most of our apps have a strong CRUD aspect, converting to React on these areas would suck. I'll definitely keep an eye out on Unform, it looks great!
Yeah! The Unform was designed to keep it simple. And even be powerful (the devs are thinking about so many good features to add). It's really worth it.
As I fell in love with
styled-components
, the lack of support of it inunform
is a no for me right now. But I see they have it in their roadmap so I'll definitely try it out :DYeah. There is already a pull request adding styled components feature. Stay tuned for future versions.
Have you heard of Informed? It's another simple React form library (previously called react-forms), but has some mature features since it's older. If you look at it, what would you say is the difference between you and them?
Well, I didn't know Informed. I saw it now, and seems good too. But, things that I realized: 1) Unform hits 1k stars in three days; 2) the company behind Unform (Rocketseat) has great developers, I know them, they teach programming and has a big community involved, with about 59k of students, suggesting features and contributing; 3) Unform hits 100% of tests coverage (since Informed has 78%); 4) Unform was designed with performance in mind too, not only simplicity. So, don't get me wrong, Informed seems to be a good choice too. But I really liked Unform and its support. Feel free to try it out too and choose the best to your needs. And thanks for your comment! :D
Thanks for sharing Unform, I have built a custom hook for form validation. Please check it out when u have sometimes as well :) react-hook-form.com/
Forms is probably one of the things I preferred in angular when I moved over to react... But this looks interesting, thanks for sharing it!
We share this thought. But maybe Unform breaks the wheel.