How to Validate Forms in React with Formik and Styled Components
Creating and validating forms in React can get pretty complicated. You need to save the values in state, handle input validation and error messages, and also control the form submission. Fortunately, libraries such as Formik can help reduce the complexity so that you can set-up your form much faster. This is what you’ll learn in this tutorial: how to validate a form with Formik and style it with Styled Components.
The solution
If you’d like to lean how to build the form in this tutorial keep reading, otherwise feel free to dig into the code. You can find the live version on codesandbox and the repo on Github.
Setup and Instalation
The easiest way to get started is to create a new project with create react app.
You can do so by running in your terminal
npx create-react-app <your-projects-name>
or just npx create-react-app .
to
install everything in your current folder, if it is empty.
Then, you need to install Formik, Yup (for handling the form validation) and styled-components.
To install them simply run these commands in your terminal:
npm install formik yup styled-components
Once you have installed everything, you can run npm start
to start the
development server.
Creating the Form
Formik gives you many options to control the forms in your app. You could either use all the helpers included with the library —which is what I will do in this tutorial—, or you can write everything yourself and connect the input fields and form to the Formik methods and event handlers.
I think that the easiest way is to use the helpers it offers because that way you can abstract away much of the complexity.
To get started, you need to import the Formik components used to render the form and inputs.
import { Formik, Field, Form, ErrorMessage } from "formik";
The basic boilerplate is that we need to use the <Formik />
component that
handles the form validation and submission. Then, we use the <Form />
and
<Field />
components to render the form and the input elements.
import React from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
function App() {
return (
<div>
<h1>React form validation with formik and styled components</h1>
<Formik
initialValues={{
fullname: "",
email: "",
}}
validationSchema={Yup.object().shape({
fullname: Yup.string()
.min(2, "Your name is too short")
.required("Please enter your full name"),
email: Yup.string()
.email("The email is incorrect")
.required("Please enter your email"),
})}
onSubmit={(values, { setSubmitting }) => {
const timeOut = setTimeout(() => {
console.log(values);
setSubmitting(false);
clearTimeout(timeOut);
}, 1000);
}}
>
{({
values,
errors,
touched,
handleSubmit,
isSubmitting,
validating,
valid,
}) => {
return (
<Form name="contact" method="post" onSubmit={handleSubmit}>
<label htmlFor="fullname">
Fullname
<Field
type="text"
name="fullname"
autoComplete="name"
placeholder="your fullname"
/>
</label>
{errors.fullname && touched.fullname && <p>{errors.fullname}</p>}
<label htmlFor="email">
Email
<Field
type="email"
name="email"
autoComplete="email"
placeholder="your email"
/>
</label>
<ErrorMessage name="email">{(msg) => <p>{msg}</p>}</ErrorMessage>
<button type="submit" disabled={!valid || isSubmitting}>
{isSubmitting ? `Submiting...` : `Submit`}
</button>
</Form>
);
}}
</Formik>
</div>
);
}
export default App;
With this, we have a basic boilerplate of a contact form made with Formik and React, but first, let’s see what we have so far.
<Formik
initialValues={{
fullname: "",
email: "",
}}
validationSchema={Yup.object().shape({
fullname: Yup.string()
.min(2, "Your name is too short")
.required("Please enter your full name"),
email: Yup.string()
.email("The email is incorrect")
.required("Please enter your email"),
})}
onSubmit={(values, { setSubmitting }) => {
const timeOut = setTimeout(() => {
console.log(values);
setSubmitting(false);
clearTimeout(timeOut);
}, 1000);
}}
>
...
initialValues
is an object that stores the values the input fields of the form
will have initially. In this example, they are fullname
and email
(you can
add as many as you’d like).
validationSchema
is an object that holds the Yup validation schema defined
that represents the validation rules for each of the input fields of the form.
In this case, we have a fullname
text field with a minimum length of 2
characters that is also required. We defined these requirements by using the
Yup.string()
methods. If you’d like to see all the other methods available on
the Yup schema validator you can
read their docs. Finally, the onSubmit
event
handler is used to control the submission of the form
The Formik components use a render props approach to render the form and input fields. It returns several boolean values and event handlers which you can use to control the form.
You just need to connect the regular form onSubmit
event to the onChange
handler provided by Formik, and to use the Field component instead of the
regular HTML input element.
Then, once the user starts typing, the validation be executed on each of the fields.
Styling the Form
So far we can render the form and its elements but we still need to style it.
You can use your CSS, but if you’d like to follow along with the tutorial copy
these styles in a new styles.js
file and import in the App.js
file.
Your styles.js
file should look like this:
import styled, { css } from "styled-components";
import { Field } from "formik";
export const PageWrapper = styled.section`
&,
& * {
box-sizing: border-box;
display: block;
}
hr {
display: block;
border: none;
border-top: 1px solid lightgrey;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
font-family: system-ui;
font-size: 1rem;
line-height: 1.5rem;
max-width: 35em;
margin-left: auto;
margin-right: auto;
margin-top: 1.5rem;
padding: 1rem 0.75rem;
border: 1px solid lightgrey;
border-radius: 4px;
`;
export const CodeWrapper = styled.pre`
font-family: monospace;
font-size: 0.875rem;
line-height: 1.25rem;
background-color: hsl(210, 4%, 96%);
overflow: auto;
padding: 0.75rem;
margin: 0;
border-radius: 4px;
& strong {
margin-top: 1.5rem;
&:first-child {
margin-top: 0;
}
}
`;
export const Title = styled.h1`
font-size: 1rem;
line-height: 1.25rem;
margin-top: 0;
`;
export const Label = styled.label`
margin-top: 1.5rem;
width: 100%;
`;
export const Input = styled(Field)`
background-color: white;
border: 1px solid lightgrey;
border-radius: 4px;
font-size: 1rem;
line-height: 1.5rem;
font-style: normal;
font-weight: 400;
width: 100%;
margin-top: 0.5rem;
padding: 0.75rem 0.75rem;
&:focus,
&:active {
box-shadow: rgb(210, 213, 217) 0px 0px 2px 1px, rgb(227, 230, 232) 0px 0px 0px
3px;
border: 1px solid rgb(26, 33, 43);
outline: none;
}
/* Autocomplete styles in Chrome*/
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus {
background-color: white;
border: 1px solid lightgrey;
box-shadow: 0 0 0px 1000px #fff inset;
-webkit-box-shadow: 0 0 0px 1000px #fff inset;
transition: background-color 5000s ease-in-out 0s;
-webkit-text-fill-color: black;
}
${({ valid }) =>
valid &&
css`
border: 1px solid rgb(0, 156, 38);
&:focus,
&:active {
border: 1px solid rgb(0, 156, 38);
box-shadow: rgb(106, 237, 97) 0px 0px 2px 1px, rgb(177, 247, 160) 0px 0px
0px 3px;
outline: none;
}
/* Autocomplete styles in Chrome*/
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus {
border: 1px solid rgb(0, 156, 38);
}
`}
${({ error }) =>
error &&
css`
border: 1px solid rgb(191, 49, 12);
outline: none;
&:focus,
&:active {
box-shadow: rgb(244, 129, 116) 0px 0px 2px 1px, rgb(251, 178, 174) 0px 0px
0px 3px;
border: 1px solid rgb(191, 49, 12);
outline: none;
}
/* Autocomplete styles in Chrome*/
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus {
border: 1px solid rgb(191, 49, 12);
}
`}
`;
export const StyledInlineErrorMessage = styled.div`
background-color: rgb(255, 245, 245);
color: rgb(120, 27, 0);
display: block;
padding: 0.5rem 0.75rem;
margin-top: 0.5rem;
white-space: pre-line;
`;
export const Submit = styled.button`
width: 100%;
margin-top: 1.5rem;
background-color: rgb(24, 81, 187);
display: block;
text-align: center;
font-size: 1rem;
line-height: 1.5rem;
font-style: normal;
font-weight: 700;
height: 3rem;
white-space: nowrap;
color: rgb(232, 243, 255) !important;
padding: 0.5rem 1rem;
&:active,
&:focus,
&:hover {
cursor: pointer;
}
&:disabled {
cursor: pointer;
background-color: rgb(163, 168, 173);
box-shadow: none;
color: rgb(255, 255, 255) !important;
&:hover,
&:focus {
cursor: not-allowed;
}
}
`;
And the App.js
file should look like this:
import React, { useState } from "react";
import { Formik, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import {
PageWrapper,
Title,
Label,
Input, StyledInlineErrorMessage,
Submit,
CodeWrapper,
} from "./styles";
function App() {
const [formValues, setFormValues] = useState();
return (
<PageWrapper>
<Title>
React form validation with formik and styled components demo
</Title>
<hr />
<Formik
initialValues={{
fullname: "",
email: "",
}}
validationSchema={Yup.object().shape({
fullname: Yup.string()
.min(2, "Your name is too short")
.required("Please enter your full name"),
email: Yup.string()
.email("The email is incorrect")
.required("Please enter your email"),
})}
onSubmit={(values, actions) => {
console.log(values);
setFormValues(values);
const timeOut = setTimeout(() => {
actions.setSubmitting(false);
clearTimeout(timeOut);
}, 1000);
}}
>
{({
values,
errors,
touched,
handleSubmit,
isSubmitting,
validating,
valid,
}) => {
return (
<>
<Form name="contact" method="post" onSubmit={handleSubmit}>
<Label htmlFor="fullname">
Fullname
<Input
type="text"
name="fullname"
autoCorrect="off"
autoComplete="name"
placeholder="your fullname"
valid={touched.fullname && !errors.fullname}
error={touched.fullname && errors.fullname}
/>
</Label>
{errors.fullname && touched.fullname && (
<StyledInlineErrorMessage>
{errors.fullname}
</StyledInlineErrorMessage>
)}
<Label htmlFor="email">
Email
<Input
type="email"
name="email"
autoCapitalize="off"
autoCorrect="off"
autoComplete="email"
placeholder="your email"
valid={touched.email && !errors.email}
error={touched.email && errors.email}
/>
</Label>
<ErrorMessage name="email">
{(msg) => (
<StyledInlineErrorMessage>{msg}</StyledInlineErrorMessage>
)}
</ErrorMessage>
<Submit type="submit" disabled={!valid || isSubmitting}>
{isSubmitting ? `Submiting...` : `Submit`}
</Submit>
</Form>
<hr />
<CodeWrapper>
<strong>Errors:</strong> {JSON.stringify(errors, null, 2)}
<strong>Touched:</strong> {JSON.stringify(touched, null, 2)}
{formValues && <strong>Submitted values:</strong>}
{JSON.stringify(formValues, null, 2)}
</CodeWrapper>
</>
);
}}
</Formik>
</PageWrapper>
);
}
export default App;
The main changes are that we are now using a <Input />
component which is just
the <Field />
component from Formik but wrapped with Styled Components so that
we can add our styles to it. We are also importing it from the styles.js
file
and not from the Formik library as before.
Displaying Error Messages
In order to display the error messages when the validation fails, Formik gives
you several ways to do it. First, you can use the <ErrorMessage />
component
which expects a name
prop that holds the name of the input element in the
form. This name has to match the name provided to the validation schema and
initial values object.
<ErrorMessage name="email">
{(msg) => <StyledInlineErrorMessage>{msg}</StyledInlineErrorMessage>}
</ErrorMessage>
The other way is to use the errors
object returned by the <Formik />
component from the render props.
{
errors.fullname && touched.fullname && (
<StyledInlineErrorMessage>{errors.fullname}</StyledInlineErrorMessage>
);
}
With this, we are now displaying the error messages if the validation fails for any of the input fields.
To also change the styling of the input field we are passing in an valid
and
an error
prop to styled-components to change the CSS.
<Label htmlFor="fullname">
Fullname
<Input
type="text"
name="fullname"
autoCorrect="off"
autoComplete="name"
placeholder="your fullname"
valid={touched.fullname && !errors.fullname}
error={touched.fullname && errors.fullname}
/>
</Label>
Then, inside the styled-component component we can change the CSS when the
valid
or error
props are set to true
.
${({ error }) =>
error &&
css`
border: 1px solid rgb(191, 49, 12);
outline: none;
&:focus,
&:active {
box-shadow: rgb(244, 129, 116) 0px 0px 2px 1px,
rgb(251, 178, 174) 0px 0px 0px 3px;
border: 1px solid rgb(191, 49, 12);
outline: none;
}
/* Autocomplete styles in Chrome*/
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus {
border: 1px solid rgb(191, 49, 12);
}
`}
These CSS styles just change the border color and the box-shadow
so that it is
more obvious to the user that the value entered is incorrect.
However, if you type any value in the input fields you will receive the following error in the console:
index.js:1375 Warning: Received `true` for a non-boolean attribute `valid`.
If you want to write it to the DOM, pass a string instead: valid="true" or valid={value.toString()}.
in input (created by FieldInner)
in FieldInner (created by Context.Consumer)
in FormikConnect(FieldInner) (created by Context.Consumer)
Filtering Props That Are Not Known HTML Attributes
This error happens because when using the styled.div
syntax, styled-components
only passes through known HTML attributes. However, when using the
styled(ComponentName)
syntax, it passes through all props, even if they are
not valid HTML attributes. This is why React warns us that we are passing
invalid attributes to the DOM element.
To solve this we can use an intermediate component which I called
<FilteredPropsInputField />
(best name I could come up with 🤷‍♂️) which captures
all the props passed and only renders on the DOM node the valid attributes.
import React from "react";
import { Field } from "formik";
function FilteredPropsInputField({ className, valid, error, ...props }) {
return <Field className={className} {...props} />;
}
export default FilteredPropsInputField;
In our case since we are only passing in valid
and error
as props that are
not HTML attributes, we destructure them along with the className
and other
props. Then, we only attach the className
and props
to the Field
component
so that it doesn’t receive any props that are not known HTML attributes.
Now, rather than using a styled(Field)
component in the styles.js
file, we
use a styled(FilteredPropsInputField)
.
import FilteredPropsInputField from "./FilteredPropsInputField";
...
export const Input = styled(FilteredPropsInputField)`
background-color: white;
border: 1px solid lightgrey;
...
${({ valid }) =>
valid &&
css`
border: 1px solid rgb(0, 156, 38);
...
${({ error }) =>
error &&
css`
border: 1px solid rgb(191, 49, 12);
outline: none;
&:focus,
&:active {
...
With these changes, we now have a working form made with Formik and styled with styled-components 🎉.
Takeaways
Now you should also be able to set-up your form in React with ease. When I started working on the contact form on this site, I wasn’t sure how to use all the components in the Formik library. The most challenging part for me was understanding how I can validate each input field individually. I wanted to style each of them according to their current state, but I wasn’t sure how to. Then, once I understood how to mix styled-components and the component that filtered the non-standard HTML attributes it was pretty easy to set-up.