If you're a React developer, you know that creating forms can be a huge hassle: you have to set up state for each form field, then add value
and onChange
props to each input, not to mention state for errors and error messages for each field. Phew!
Luckily, there's a better way to do this - we can use a library called Formik to manage our React forms. With Formik, the same form page can be created with drastically fewer lines of code, since Formik
- manages all of the state for us and
- takes care of validation and error messages, with the help of Yup.
In this series, we're going to be creating this registration form for a developer only social network.
The next post will be all about adding validation with Yup, and this one will mainly focus on using Formik to create a barebones form.
Creating the form skeleton and the styles
Since this isn't a HTML and CSS tutorial, I won't be focusing much on this part of the build, though you're welcome to take a closer look at it. Here is the JSX for the form container and the header text:
<div className="form-container">
<h1 className="greeting heading">
Hey there{" "}
<span role="img" aria-label="wave emoji">
๐๐ผ
</span>
</h1>
<h2 className="heading cta">Let's get you on board!</h2>
</div>
I'll put the styles at the end of the post, since they're a bit long and will distract from the main focus of the article if I put them here. Speaking of which, let's dive right in to making the form!
The Formik
context
This component is a context provider that manages the state for the form inputs and their errors. Its children - the Form
, Field
and ErrorMessage
components - use this context under the hood to link to a certain field's state. We use it like so:
<Formik
initialValues={{
firstName: "",
email: "",
group: "",
bio: "",
github: "",
username: "",
password: "",
passwordTwo: "",
tnc: false
}}
onSubmit={values => {
console.log(values);
alert("Registered successfully");
}}
>
</Formik>
There are two things to note here.
The
initialValues
prop takes in an object that defines the initial values (surprise, surprise ๐) for each of your fields. Note the use of double curly braces, 1 pair for the JavaScript expression in JSX and another for the object.The
onSubmit
prop takes in a function that will be called when the form is submitted. An object containing all of the values of your fields will be passed to this function. Here, I've justconsole.log
ged them, but this could just as easily be anaxios
orfetch
request. A really great thing about Formik is that you don't need to usee.preventDefault()
to prevent the page from refreshing - Formik does that automagically!
The Form
component
To be honest, there really isn't much to this component, which is nested inside the Formik
provider. For all intents and purposes, we can treat it just like the form
element in HTML.
<Form classname="form" autoComplete="off">
</Form>
As you can see, it can take the same props as the attributes of the form
element. Apart from autoComplete
, you can also use autoValidate
, etc. Inside it, we'll add all the form fields.
Field
and ErrorMessage
<div>
<label htmlFor="firstName">First name*</label>
<Field name="firstName" />
<p className="error">
<ErrorMessage name="firstName" />
</p>
</div>
The Field
component, which returns an input
element, is fairly straightforward and it takes one prop called name
which links it to the state we set up in the initialValues
prop in the Formik
component. Therefore, it is essential that you use the same identifier in both places.
The ErrorMessage
component is quite self-explanatory. It only comes into play when we add validation (which we'll do in the next post in this series!) but it is very easy to understand. Just like Field
, it has a name
prop, where we pass in one of our field names from the initialValues
. What's more interesting is why it's nested inside a p
tag. This is because it returns a string, not JSX, and this is rendered to the DOM as a text node, which can't be styled using CSS. So, we have to put it inside an HTML element that can be styled; I've used a p
here, but we can also use span
or other text tags.
One more thing: I've put the label
, Field
and ErrorMessage
inside a div
just to make it easier to style. I find that this really helps when we use things like CSS grid to position the fields.
Radio buttons
The code for the email field is exactly the same, but the next field consists of radio buttons, which are actually quite easy to implement in Formik.
<div className="left-align">
<label htmlFor="group">I am a*</label>
<label className="radio-label">
<Field
className="radio"
type="radio"
name="group"
value="frontend"
/>
Front-end engineer
</label>
<label className="radio-label">
<Field
className="radio"
type="radio"
name="group"
value="backend"
/>
Back-end engineer
</label>
<label className="radio-label">
<Field
className="radio"
type="radio"
name="group"
value="fullstack"
/>
Fullstack engineer
</label>
</div>
Each radio button option is placed inside its own label. It uses the same Field
component, but we've got a few more props this time.
className
-> This is used to style the fieldname
-> This is the same as in the other fields, and links the buttons to the state. It has to be the same for each radio button in a group.type
-> This tells Formik to return radio buttons instead of input fields. It can also take the value of "checkbox", as we'll see later.value
-> This is just like thevalue
attribute in HTML. It specifies what value the field's state should take when an option is selected.
Textareas
<div>
<label htmlFor="bio">Tell us a bit about yourself</label>
<Field
component="textarea"
name="bio"
placeholder="Your bio..."
/>
<p className="error">
<ErrorMessage name="bio" />
</p>
</div>
The component
prop tells the Field
component which HTML element to return - in this case, a textarea. Also notice the placeholder
prop, which can be used in any Field
except radio buttons and checkboxes.
Checkboxes
Since the GitHub, username and password fields are just normal inputs, there's nothing to much to look at there. It's the same thing as the name and email fields, albeit the name
props are different. Instead, let's add a checkbox asking the user to agree to the terms and conditions.
When we wrote the initial value for the T&C field, it was a boolean value and not a string, as you may have noticed. This is because there are only two values it can take: either the user accepts the T&C or they don't. The syntax is very similar to that of a radio button:
<div className="tnc">
<label htmlFor="tnc">
<Field className="checkbox" type="checkbox" name="tnc" />I
accept the terms and conditions
</label>
<p className="error">
<ErrorMessage name="tnc" />
</p>
</div>
It has the name
and type
props, but it doesn't have a value
prop, because it can only either be true or false.
And finally, we have the submit button, which is exactly the same as normal HTML.
<button type="submit">Let's get started!</button>
At this point, your form should look exactly the same as in the CodeSandbox embed, sans the validation of course. Yay! We just built a form - and a good-looking one at that, if I do say so myself ๐ - with Formik, with much less effort and repetition than if we had made it ourselves just with JSX.
If you found this helpful, share it with devs who might need it and stay tuned for the next post in this series, where we'll use Yup to add validation to the form!
Oh, and as promised, here are the styles for this project:
@import url("https://fonts.googleapis.com/css2?family=Roboto+Slab&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
* {
box-sizing: border-box;
}
.App {
font-family: sans-serif;
}
.form-container {
max-width: 400px;
margin: 50px auto;
border-top: 5px solid #31e6ff;
box-shadow: 0px 10px 15px -3px rgba(26, 26, 26, 0.25);
padding: 10px;
}
.heading {
font-family: "Roboto Slab", sans-serif;
text-align: center;
margin: 5px 0px;
}
.cta {
margin-bottom: 15px;
}
.form {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
input {
height: 50px;
width: 300px;
padding: 5px 10px;
border: none;
margin: 5px 0px;
border-radius: 5px;
background-color: #f2f2f2;
}
input:focus,
textarea:focus {
outline: none;
border-radius: 5px 5px 0px 0px;
border-bottom: 3px solid #31e6ff;
}
label {
font-family: Roboto, sans-serif;
font-size: 12px;
display: block;
}
textarea {
font-family: "Roboto", sans-serif;
min-width: 300px;
max-width: 300px;
max-height: 200px;
height: 50px;
padding: 15px;
border: none;
margin: 5px 0px;
border-radius: 5px;
background-color: #f2f2f2;
}
.left-align {
width: 300px;
}
.radio {
height: 15px;
width: 15px;
margin-right: 10px;
}
.radio-label {
padding: 5px;
display: flex;
align-items: center;
}
.radio-label:last-of-type {
margin-bottom: 15px;
}
.checkbox {
height: 15px;
width: 15px;
border-radius: 3px;
margin: 0px 10px 15px 10px;
}
.error {
min-height: 15px;
color: #cc0000;
font-size: 12px;
margin: 0px;
margin-bottom: 15px;
}
.submit {
background: #31e6ff;
border: none;
padding: 10px 20px;
border-radius: 5px;
color: #ffffff;
font-family: "Roboto", sans-serif;
font-size: 18px;
margin: 20px 0px;
transition: all 200ms;
}
.submit:hover {
cursor: pointer;
background-color: #00ccff;
box-shadow: 0px 6px 10px -1px rgba(26, 26, 26, 0.25);
}
All this does is import a few fonts, style the form container for its shadow and top border, and then style the form fields.
TTFN, Hari!