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
InputField
component we will use anif statement
to determine what type of input element we want to render.Our component will receive multiple
props
and the first one isprops.type
. Among other places, we will usetype
to 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
else
statement we have added our first possible input field to render.It is wrapped in a
<label>
, with aprops.label
so 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
onChange
holdsprops.onChangeHandler
which passes back the input field's data to the parent form component.The
type
holds theprops.type
. In this instance, it is used to tell if this field's functionality should be for an email, text, tel, etcThe
placeholder
holds theprops.placeholder
string and will show some greyed-out text before the user types.The
value
holds theprops.value
which is actually the parent passing back in theonChangeHandler
. This will show the text inside the field in a controlled way.The
required
holds 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
name
is 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 if
statement 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
rows
does 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
if
statement 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.label
because this is technically the label or button text. (Such as "Submit", "Send", "Confirm", etc)The
disabled
method 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
InputForm
componentsWe 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
onSubmit
on 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
InputField
components and pass in the prop values that we previously set up.Note how the last
<InputField />
takes in an array on theformValues
prop, 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 === someType
on 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.