Integrating styled forms in ReactShukant PalBlockedUnblockFollowFollowingJun 22React+Formik+Yup+[CSS]=Simple+styled form boilerplateAs a React developer, you have plenty of options on how you’re going to build forms in your application.
For a simple one-time use-case, you might decide to get your hands dirty by directly building a form-wrapper component.
However, if you need multiple forms, then you might use a third-party library like Formik.
Practically, you need to know both methods.
This article will help you understand how to do exactly that.
In addition, I also go over some “design” tips for your forms.
Raw HTML5 formsHTML5 + vanilla JavaScript work perfectly fine until the form gets too large.
Generally, you will use nested form input elements like text , button , password , textarea , and more.
In the end, you add a “submit” button that validates and then processes the form.
<form> <label htmlFor="emailAddress">Enter email:</label> <input type="text" name="emailAddress" /> <label htmlFor="password">Password:</label> <input type="password" name="password" /> <input type="submit" value="Submit" /></form>The name attribute identifies the field being filled by the user.
This is “required” when you send the form to be processed by the server using the form action attribute.
However, if you are validating and processing the form in JavaScript, then you can use the name attribute to identify the field in your onchange handler.
// <input type="text" name="emailAddress"// onchange="handleChange"/>function handleChange(e) {// included using <script> if (e.
target.
name === 'emailAddress') console.
log("emailAddress changed");}Caveats associated with raw HTML5 formsForm validation: JavaScript was originally made for client-side form validation.
You most probably will need that for your forms too.
However, implementing generic form validation is a waste of time because it is already implemented in third-party libraries.
Boilerplate code: You will build up a lot of boilerplate with forms.
For example, each field will require onchange and onblur handlers and then error messages below it.
The code for a login form can easily reach up to 500 lines of code (without styling).
React FormsReactJS is a perfectly valid solution for HTML5 forms.
After all, its main goal is to synchronize the browser DOM with your data.
You can build a form-wrapper component which stores field-values in its state.
Hence, separating form validation & submission from the actual form markup.
To keep field values in the wrapper component’s state, you need to make the component “controlled”.
“In HTML, form elements typically maintain their own state and update it based on user input.
In React, mutable state is typically kept in the state property of components and only updated with setState().
We can combine the two by making the React state be the ‘single source of truth’.
”— React DocsHow do you make a “single source of truth”?.This is accomplished by controlling the value of each input — by using onChange to set the value in our React state.
Then binding the input’s state to the React state.
class LoginForm extends React.
Component { constructor(props) { super(props); this.
state = { username: '', password: '', errors: { username: '', password: '' } } this.
handleChange = this.
handleChange.
bind(this); this.
handleSubmit = this.
handleSubmit.
bind(this); } validateUsername(s) { // returns true/false based on user-name validity } validatePassword(s) { // returns true/false based on password validity } handleChange(e) { if (e.
target.
name === 'username') { this.
state.
username = e.
target.
value; if (!validateUsername(e.
target.
value)) this.
state.
errors.
username = "Invalid username"; } else if (e.
target.
name === 'password') { this.
state.
password = e.
target.
value; if (!validatePassword(e.
target.
value)) this.
state.
errors.
password = "Invalid password"; } } render() {// finally return ( <form> <input type="text" onChange={{this.
handleChange}} value={this.
state.
username} /> <span>{this.
errors.
username}</span> <input type="password" onChange={{this.
handleChange}} value={this.
state.
password} /> <span>{this.
errors.
password}</span> </form> ); }}This component will essentially re-render every time the user inputs something new.
However, it provides the ability to:Edit user-input: You can correct the input by intercepting the new value in handleChange .
this.
state.
username = e.
target.
value.
toUpperCase();Check for errors/availabilityUncontrolled componentsInstead of making the React state “the single source of truth”, you can make the DOM “the single source of the truth” using uncontrolled components.
Here, you don’t update the state in handleChange , rather you always reference the DOM input node to get the value.
This is done using a ref to the input nodes.
A ref is essentially a reference to a node in React.
You can alter the referenced node outside the normal data flow (without passing new props).
class LoginForm { constructor(props) { super(props); this.
usernameRef = React.
createRef(); } render() { return ( <form> <input ref={this.
usernameRef} /> </form> ); }}The usernameRef property stores the rendered input DOM node.
Formik to the rescueJared Palmer, the creator of Formik, wrote the library to standardize form-wrapping components.
It reduces a ton of boilerplate code associated with forms and allows you to focus on other aspects of your design.
“With around ~30 unique forms, it quickly became obvious that we could benefit by standardizing not just our input components but also the way in which data flowed through our forms.
” — Jared PalmerConceptFormik exposes its Formik component that manages your forms state and data flow.
You are required to provide the children to this component using the props it passes to you.
I’ll discuss a bare minimum example that will help you understand the process.
<Formik initialValues={{ username: '', password: '' }} validationSchema={/* We'll discuss this below!.*/} validate={/* We'll discuss this below!*/} onSubmit={/* We'll discuss this shortly!.*/} render={({handleChange, onSubmit, values, errors}) => ( <form> <input type="text" name="username" onChange={handleChange} value={values.
username}/> {errors.
username} <input type="password" name="password" onChange={handleChange} value={values.
password} /> {errors.
password} <input type="button" onClick={onSubmit} value=")} /> </form> )}/>The most important of the code above is the render prop.
The render prop should be a function that takes an object of props given by Formik.
It returns a React component, which is your form.
There is a full list of props provided by Formik; however, you can declare the fields that you’ll use.
values : This is a field-name to field-value map.
Its initial value is given by the initialValue prop given.
handleChange : This event-handler does what we did in our custom React form component.
It updates the values object when the user inputs something new.
onSubmit : This is a function you provide that submits the form given the values.
Props you should provide:validate/validationSchema: validate is a function that returns any errors in the form values.
It returns an object that maps field-names to error messages.
An alternative option is to use Yup, which is an object schema validation library.
validationSchema is an object which comes from Yup.
Yup in the mixJared Palmer recommends Yup as a validation tool.
That is because of its convenience and clarity.
An example of a schema created from Yup:// npm install –save yupimport * as Yup from 'yup'const loginSchema = Yup.
object().
shape({ username: Yup.
string() .
email('Username should be an email) .
required('Username is required!'), password: Yup.
string() .
required('Password is required!')};You can validationSchema={loginSchema} and then the errors will be updated automatically when the user types/updates the inputs.
They will be displayed via the text nodes.
Designing your first formThe desktop version of my sign up for CloudVoteIn the forms we’ve developed so far, we haven’t guided the user on what to fill and how to fill.
We have to add labels and visually design our form to look appealing.
It’s hard for me to build anything that looks “stunning”; however, we could aim to build something decent as a developer.
The design is not as simple as it seems.
It is also important to know that labels are always aligned to the right.
It forms an axis-like line in the middle.
This has actually been studied even by Google developers, and it helps the user to fill the form faster.
The form that I’ve shown uses a table and aligns label/text-field combinations in each row.
Alongside, I’ve used CSS to style each element.
A template for the form inside the Formik component will look something like:<form className="blackbox-form"> <table> <tbody> <tr> <td> <label htmlFor="emailAddress">E-mail Address</label> </td> <td> <input name="emailAddress" type="text" onChange={handleChange} value={values.
emailAddress}/> <span>{errors.
emailAddress}</span> </td> </tr> /* Rows for organization, passwords, submit-button too!!!.*/ </tbody> </table></form>Using the tbody tag is necessary because the browser will automatically add it to the DOM and React may complain that you’re code is inconsistent with the DOM.
However, “not” using it will “not” break your application due to the flexibility built into React.
Mobile friendly formsMobile version of the formA mobile-friendly form usually looks like the one shown.
The labels are placed a little above the text fields and are much smaller (and bold).
A responsive design will incorporate both the desktop and mobile versions of the form.
Let’s look at how we could build something like that!<form className="blackbox-form"> <table> <tbody> <tr> <td className="wide-screen-label-cell"> <label htmlFor="emailAddress">E-mail Address</label> </td> <td className="mobile-screen-label-cell"> <label htmlFor="emailAddress">E-mail Address</label> <input name="emailAddress" type="text" onChange={handleChange} value={values.
emailAddress}/> <span className="errors">{errors.
emailAddress}</span> </td> </tr> </tbody> </table></form>In our JSX, we include labels in both the left column and on top of the input text field.
However, our CSS styling will disable at least one.
wide-screen-label-cell { display: none; text-align: right;}.
mobile-screen-label-cell label { font-size: 13px; font-weight: bold;}By default (and according to the mobile-first principle), our CSS only displays the mobile version of the form.
It hides the wide-screen label column cells.
A CSS media query can be used to revert this for wide-screen devices:@media only screen and (min-width: 600px) { .
wide-screen-label-cell { display: block; } .
mobile-screen-label-cell label { display: none; }}Styling our errors is also crucial — they should be red and a little smaller.
blackbox-form .
errors {// our form is classed as blackbox-form color: red; font-size: 12px; font-style: italics;}Interactive designYou should always make your form a little more interactive and responsive to user actions.
I like to make my text-fields a horizontal line when they are in focus.
My CloudVote sign up form.
Room for improvement still exists.
I hope this article gives you more insight into HTML+React forms.
I am Shukant Pal — the creator of the Silcos kernel.
Further reading:A full overview of the HTML CanvasHow non-integer values are stored in a float (and why it floats)Removing circular dependencies in JavaScriptHow to synchronize your app across multiple devices (Android)How to use Firebase for building multiplayer Android games.