Three for the price of one...
In this post, we are going to build a robust contact form with validation – using one input field component!
Why is this helpful? If you ever need to change the styles or functionality globally, you can do so in just this file.
I'd love to show you how it works today!
What we are going to build:
How to build the component
We are going to start by building our custom component InputField. Once that is set up, we will look at styling and the parent Form component that holds everything.
Steps
1 — Set up InputField base code
import React from 'react';
import './inputFieldStyles.scss';
const InputField = props => {
if (props.type === "submit") {
return (
)
} else if (props.type === "textarea") {
return (
);
} else {
return (
);
}
};
export default React.memo(InputField);
Breakdown
We start by importing React and an SCSS stylesheet.
Inside our
InputFieldcomponent we will use anif statementto determine what type of input element we want to render.Our component will receive multiple
propsand the first one isprops.type. Among other places, we will usetypeto choose the correct input.At the bottom, we export the component and wrap around the Higher-Order React component
memo. This will make sure our component won't re-render if its props haven’t change.
2 — Add the first input field into the if statement
import React from 'react';
import './inputFieldStyles.scss';
const InputField = props => {
if (props.type === "submit") {
return (
)
} else if (props.type === "textarea") {
return (
);
} else {
return (
<label className="inputField__label">
{props.label}
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={props.type}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className="inputField__field"
name={props.name}
/>
</label>
);
}
};
export default React.memo(InputField);
Breakdown
Starting from the bottom
elsestatement we have added our first possible input field to render.It is wrapped in a
<label>, with aprops.labelso we can dynamically pass in a name as a string. This text will appear above the form field and will also focus on the field if clicked.The
onChangeholdsprops.onChangeHandlerwhich passes back the input field's data to the parent form component.The
typeholds theprops.type. In this instance, it is used to tell if this field's functionality should be for an email, text, tel, etcThe
placeholderholds theprops.placeholderstring and will show some greyed-out text before the user types.The
valueholds theprops.valuewhich is actually the parent passing back in theonChangeHandler. This will show the text inside the field in a controlled way.The
requiredholds a boolean, which is passed in viaprops.isRequired. If this is added in the parent component, the field will be required. If left off it won't.The
nameis passed in viaprops.name. This is especially helpful with a Netlify mail server.
3 — Add the second input field into the if statement
import React from 'react';
import './inputFieldStyles.scss';
const InputField = props => {
if (props.type === "submit") {
return (
)
} else if (props.type === "textarea") {
return (
<label className="inputField__label">
{props.label}
<textarea
onChange={(e) => props.onChangeHandler(e.target.value)}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className="inputField__field"
rows={7}
name={props.name}
/>
</label>
);
} else {
return (
<label className="inputField__label">
{props.label}
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={props.type}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className="inputField__field"
name={props.name}
/>
</label>
);
}
};
export default React.memo(InputField);
Breakdown
Moving up to the
else ifstatement we have now added our<textarea>field to render.The props it receives are very similar to the input field below it, with one addition.
The
rowsdoes not receive a prop in my example, but totally can if you wish to make it dynamic. The number placed as its value will determine how tall the<textarea>is. The above example will support 7 lines of user text.
4 — Add the final input field into the if statement
import React from 'react';
import './inputFieldStyles.scss';
const InputField = props => {
if (props.type === "submit") {
return (
<input
className='primaryBtn primaryBtn--big g__justify-self-center'
type='submit'
value={props.label}
disabled={validateInput(props.formValues)}
/>
)
} else if (props.type === "textarea") {
return (
<label className="inputField__label">
{props.label}
<textarea
onChange={(e) => props.onChangeHandler(e.target.value)}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className="inputField__field"
rows={7}
name={props.name}
/>
</label>
);
} else {
return (
<label className="inputField__label">
{props.label}
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={props.type}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className="inputField__field"
name={props.name}
/>
</label>
);
}
};
export default React.memo(InputField);
Breakdown
Moving up to the top
ifstatement we have now added our<input type="submit">field to render.This input will be the submit button for our forms.
The value takes in a
props.labelbecause this is technically the label or button text. (Such as "Submit", "Send", "Confirm", etc)The
disabledmethod takes in a custom function that also passes in an array from props calledprops.formValues. This will be explained in the next step.
5 — Add input validator helper function
import React from 'react';
import './inputFieldStyles.scss';
const InputField = props => {
const validateInput = values => {
if (values.some(f => f === "") || values[0].indexOf("@") === -1) {
return true
} else {
return false
}
}
if (props.type === "submit") {
return (
<input
className='primaryBtn primaryBtn--big g__justify-self-center'
type='submit'
value={props.label}
disabled={validateInput(props.formValues)}
/>
)
} else if (props.type === "textarea") {
return (
<label className="inputField__label">
{props.label}
<textarea
onChange={(e) => props.onChangeHandler(e.target.value)}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className="inputField__field"
rows={7}
name={props.name}
/>
</label>
);
} else {
return (
<label className="inputField__label">
{props.label}
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={props.type}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className="inputField__field"
name={props.name}
/>
</label>
);
}
};
export default React.memo(InputField);
Breakdown
This function is used in the
input type="submit"disabled field.It takes in an array of all the form values. This was passed down as props from the parent component. It's important to note that the email value will always be the first item in this array.
The function checks if any of the values in the array empty using the
.some()method. If true, then the function will return true and the button will be disabled.It then checks if the email value contains an "@". If not, then the function will return true and the submit input will also be disabled.
In all other cases the function will return false and the submit input will *not be disabled. (Remember that
disabled={false}will keep the input active.)
6 — Add InputField styles
@use "../../../sassStyles/_variables" as v;
@use "../../../sassStyles/_mixins" as m;
.inputField__label {
display: grid;
grid-row-gap: 10px;
color: v.$secondary2;
font-size: 16px;
margin: 0 auto;
width: 100%;
max-width: 400px;
@include m.poppinsFontStack;
@include m.smMinBreakPoint {
font-size: 18px;
}
}
.inputField__field {
@include m.poppinsFontStack;
background-color: v.$primaryDark3;
border: none;
font-size: 16px;
padding: 16px 20px;
margin: 0 auto;
width: 100%;
max-width: 400px;
font-weight: bold;
color: v.$secondary2;
@include m.smMinBreakPoint {
font-size: 18px;
padding: 20px 25px;
}
}
::placeholder { /* Firefox */
font-weight: normal;
color: v.$primary
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: v.$primary;
font-weight: normal;
}
::-ms-input-placeholder { /* Microsoft Edge */
color: v.$primary;
font-weight: normal;
}
input[disabled] {
background-color: v.$primaryDark2;
cursor: default;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
&:hover {
background-color: v.$primaryDark2;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
transform: scale(1);
}
}
Breakdown
These styles are applied to the labels, inputs, placeholders, and even the disabled states.
I am importing SCSS mixins for pre-determined breakpoints and variables for colors. But you can easily replace them with media queries and hex color codes.
7 — Setup Contact Form parent component
import React, {useState} from 'react';
import './contactFormStyles.scss';
import InputField from "../../ui/InputField/InputField";
const ContactForm = props => {
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [message, setMessage] = useState('');
const coolFunctionHandler = e => {
// your code here
}
return (
<form className="mc__form" onSubmit={(e) => coolFunctionHandler(e)}>
</form>
)
}
export default ContactForm;
Breakdown
This component is the base for the Contact Form.
We are importing React, styles, and our custom
InputFormcomponentsWe are setting up states for each input field in our form. (Not including the submit input). These will hold the values that our users enter.
The
onSubmiton the<form>will can contain any next steps you want to happen once the form is submitted.
7 — Add our custom InputField components
import React, {useState} from 'react';
import './contactFormStyles.scss';
import InputField from "../../ui/InputField/InputField";
const ContactForm = props => {
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [message, setMessage] = useState('');
const coolFunctionHandler = e => {
// your code here
}
return (
<form className="mc__form" onSubmit={(e) => coolFunctionHandler(e)}>
<InputField
label="Name"
onChangeHandler={setName}
type="text"
value={name}
placeholder="Jane Smith"
isRequired
name="name"
/>
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder="your@email.com"
isRequired
name="email"
/>
<InputField
label="Message"
onChangeHandler={setMessage}
type="textarea"
value={message}
placeholder="How can we help..."
isRequired
name="message"
/>
<InputField
label="send"
type="submit"
formValues={[email, name, message]}
/>
</form>
)
}
export default ContactForm;
Breakdown
Now we add in our custom
InputFieldcomponents and pass in the prop values that we previously set up.Note how the last
<InputField />takes in an array on theformValuesprop, with email being the first item. This is for the validation and making sure it isn't active if there is a single missing field or invalid email entry.
Summary
It definitely took a few steps, but now you have a super robust component to use across all your website's forms! Over the long run, this setup will save a lot of time.
Happy Coding! 🤓
Thumbnail designed with Figma

Top comments (4)
what are your thoughts when we start to have more InputField types that we want to provide as a component? e.g. radio, checkbox, combo etc. how would you address the code smell of switch statements in this case?
To be honest I am not very familiar with "code smell of switch statements". Could you further elaborate on what the potential issues would be by utilizing a switch statement?
Sure! This is a good resource on the Switch Statement code smell.
In the example you have given, you were checking the if condition
props.type === someTypeon this component and return the appropriate component. For 3 types of components, the code seems fine, but if we expand the InputField component to have more types, it will become difficult to navigate this code. And for other types of InputField components, you might also want different validation instead of a catch-allvalidateInput()method.So i'm just curious how would you address these issues 😄
These are good points. To be honest I’m not sure. :) I suspect it might come down to breaking out the more complex form fields into their own components.