Let's build a 3-in-1 form field component
Tags: react, tutorial, webdev, javascript
Published: 2021-04-19 12:51:59

<a name="top"></a>

#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:
![Form Filling Out Demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obqqxjt6rwdymiy20knx.gif)


---


#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](#setup)

2. [Add the first input field into the if statement](#inputFieldOne)

3. [Add the second input field into the if statement](#inputFieldTwo)

4. [Add the final input field into the if statement ](#inputFieldThree)

5. [Add input validator helper function](#inputValidator)

6. [Add `InputField` styles](#inputStyles)

7. [Setup Contact Form parent component](#setupContactForm)

8. [Add our custom `InputField` components](#addInputFieldsToContact)


---


<a name="setup"></a>

###1 — Set up `InputField` base code

```jsx
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 an `if statement` to determine what type of input element we want to render.  

* Our component will receive multiple `props` and the first one is `props.type`. Among other places, we will use `type` 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. 

[Back to 🔝](#top)

---


<a name="inputFieldOne"></a>
###2 — Add the first input field into the if statement 

```jsx
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 a `props.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` holds `props.onChangeHandler` which passes back the input field's data to the parent form component.  

* The `type` holds the `props.type`. In this instance, it is used to tell if this [field's functionality](https://www.w3schools.com/html/html_form_input_types.asp) should be for an email, text, tel, etc 

* The `placeholder` holds the `props.placeholder` string and will show some greyed-out text before the user types. 

* The `value` holds the `props.value` which is actually the parent passing back in the `onChangeHandler`. This will show the text inside the field in a controlled way. 

* The `required` holds a boolean, which is passed in via `props.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 via `props.name`. This is especially helpful with a Netlify mail server.  


[Back to 🔝](#top)

---


<a name="inputFieldTwo"></a>
###3 — Add the second input field into the if statement 

```jsx
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. 

[Back to 🔝](#top)

---

<a name="inputFieldThree"></a>

###4 — Add the final input field into the if statement 

```jsx
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 called `props.formValues`. This will be explained in the next step. 

[Back to 🔝](#top)

---

<a name="inputValidator"></a>

###5 — Add input validator helper function

```jsx
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.)

[Back to 🔝](#top)

---


<a name="inputStyles"></a>
###6 — Add `InputField` styles

```css
@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. 

[Back to 🔝](#top)

---


<a name="setupContactForm"></a>
###7 — Setup Contact Form parent component


```jsx
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` components

* We 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. 


[Back to 🔝](#top)


---

<a name="addInputFieldsToContact"></a>
###7 — Add our custom `InputField` components


```jsx
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 the `formValues` 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.


[Back to 🔝](#top)


---

##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](https://www.figma.com/)*