Create a FormBuilder component in React Native (Part 4)
Tags: react, reactnative, ios, javascript
Published: 2019-03-18 01:07:03

---
title: Create a FormBuilder component in React Native (Part 4)
published: true
description: 
tags: react, react-native, ios, javascript
cover_image: https://thepracticaldev.s3.amazonaws.com/i/68ghwpyr8mvgumn2edca.jpeg
series: Create a FormBuilder component in React Native
---

This series contents:
- [Part 1: Create a new React Native app](https://dev.to/dev_nope/create-a-formbuilder-component-in-react-native-intro--part-1-1m84)
- [Part 2: Create a simple Salary Calculator Form](https://dev.to/dev_nope/create-a-formbuilder-component-in-react-native-part-2-2dec)
- [Part 3: Create custom form input and button components](https://dev.to/dev_nope/create-a-formbuilder-component-in-react-native-part-3-1aib)
- Part 4: Work on the `FormBuilder` component (current)
- [Part 5: Enable/disable form buttons on-the-fly](https://dev.to/dev_nope/create-a-formbuilder-component-in-react-native-part-5-535h)
- [Part 6: Create a Sign Up form](https://dev.to/dev_nope/create-a-formbuilder-component-in-react-native-part-6-2ohd)
- [Part 7: Add support for Boolean field type](https://dev.to/dev_nope/create-a-formbuilder-component-in-react-native-part-7-32c7)

# Part 4: Work on the `FormBuilder` component

We have reached the point where we have a fully functional form. Now let's try to build out a separate component which can render that form for us. We'll just need to instruct it what fields to render out and how should it handle the form submission.

### Define the form's configuration

Let's start by generating a JavaScript array which will be our `FormBuilder`'s component `configuration`. In our `App.js` file we'll define a class property as an arrow function which returns our desired configuration like so:

```javascript
getFormFields = () => {
    const inputProps = {
        placeholder: '0',
        autoCapitalize: 'none',
        autoCorrect: false,
        keyboardType: 'numeric',
        returnKeyType: 'done',
    };

    const formFields = [
        [
            {
                name: 'hourlyRate',
                label: 'Hourly Rate',
                type: 'text',
                inputProps,
            },
            {
                name: 'hoursPerWeek',
                label: 'Hours / Week',
                type: 'text',
                inputProps,
            },
        ],
        [
            {
                name: 'daysPerWeek',
                label: 'Days / Week',
                type: 'text',
                inputProps,
            },
        ],
    ];

    return formFields;
};
```

Basically, our configuration is _a list of rows with objects that describe each form field to be rendered_. Why do we need those _rows_? Simply to allow us to control the form's layout. Each form field within a row will be rendered inline, and then a new row will start from a new line. In our example, `hourlyRate` and `hoursPerWeek` will be rendered inline, but `daysPerWeek` will be rendered in a new line and will take the full width of the row.

### Modify our `handleSubmit` class property

We'll make a couple of changes to our function that handles the form submission like so:
- don't use the `App.js` component's state anymore. Because our `FormBuilder` component will take care of collecting data from our form fields, we don't need a local state anymore in our `App.js` component. Instead, we'll have a local state within our `FormBuilder` component, and that state will be passed to our `handleSubmit` function;
- remove validation for required fields. We'll delegate that job to our `FormBuilder` component as well;
- add support for our new `daysPerWeek` form field (which wasn't used in our previous blog posts);
- include `weeklyIncome` as part of the result.

The modified version should look like this:
```javascript
handleSubmit = (state) => {
    // using Javascript object destructuring to
    // get user's input data from the state.
    const { hourlyRate, hoursPerWeek, daysPerWeek } = state;

    const weeksPerYear = 52;
    const hoursPerDay = Math.ceil(parseFloat(hoursPerWeek) / parseFloat(daysPerWeek));
    const weeklyIncome = Math.abs(
        parseFloat(hourlyRate) * hoursPerDay * parseFloat(daysPerWeek),
    );
    const annualIncome = Math.abs(
        parseFloat(hourlyRate) * parseFloat(hoursPerWeek) * weeksPerYear,
    );

    // show results
    Alert.alert(
        'Results',
        `Weekly Income: $${weeklyIncome}, \n Annual Income: $${annualIncome}`,
    );
};
```

### Modify our `render()` function

Let's modify our `render()` function by removing all of the form inputs and buttons, and then use the `FormBuilder` component instead (which we'll create in just a just a moment).

```jsx
render() {
    return (
        <KeyboardAvoidingView behavior="padding" style={styles.container}>
            <Text style={styles.screenTitle}>Salary Calculator</Text>
            <FormBuilder
                formFieldsRows={this.getFormFields()}
                handleSubmit={this.handleSubmit}
                submitBtnTitle="Calculate"
            />
        </KeyboardAvoidingView>
    );
}
```

As you can see our `FormBuilder` component accepts 3 props:
- `formFieldsRows` - our desired list of form fields;
- `handleSubmit` - the function to be run when the user submits the form;
- `submitBtnTitle` - the submit button's title. We'll make this optional and have a default value within the form component itself.

### Create the `FormBuilder` component

**The time has come!** 😜🎉 Let's create our helper component 🏁. For now, we'll take a look at how the final code looks like, and then we'll walk through it and talk about functionality. **Ladies and gentlemen, may I present you the 1st version of our `FormBuilder`** 🎆🚀(I'm sorry, I just got too excited here for a second 😊):
```jsx
import React from 'react';
import PropTypes from 'prop-types';
import {
    View, StyleSheet, Keyboard, Alert,
} from 'react-native';

import FormTextInput from './FormTextInput';
import FormButton from './FormButton';

/**
 * A component which renders a form based on a given list of fields.
 */
class FormBuilder extends React.Component {
    /* eslint-disable no-param-reassign */
    constructor(props) {
        super(props);

        const formFields = this.getFormFields();

        // dynamically construct our initial state by using
        // each form field's name as an object property.
        const formFieldNames = formFields.reduce((obj, field) => {
            obj[field.name] = '';
            return obj;
        }, {});

        // define the initial state, so we can use it later on
        // when we'll need to reset the form
        this.initialState = {
            ...formFieldNames,
        };

        this.state = this.initialState;
    }
    /* eslint-enable no-param-reassign */

    /**
     * Extract our form fields from each row
     * and compose one big list of field objects.
     */
    getFormFields = () => {
        const { formFieldsRows } = this.props;
        const formFields = [];

        formFieldsRows.forEach((formFieldsRow) => {
            formFields.push(...formFieldsRow);
        });

        return formFields;
    };

    /**
     * Check if all fields have been filled out.
     */
    /* eslint-disable react/destructuring-assignment */
    hasValidFormData = () => {
        const formFields = this.getFormFields();
        const isFilled = formFields.every(field => !!this.state[field.name]);
        return isFilled;
    };
    /* eslint-enable react/destructuring-assignment */

    /**
     * Attempt to submit the form if all fields have been
     * properly filled out.
     */
    attemptFormSubmission = () => {
        const { handleSubmit } = this.props;

        if (!this.hasValidFormData()) {
            return Alert.alert('Input error', 'Please input all required fields.');
        }

        return handleSubmit(this.state);
    };

    /**
     * Reset the form and hide the keyboard.
     */
    resetForm = () => {
        Keyboard.dismiss();
        this.setState(this.initialState);
    };

    /* eslint-disable react/destructuring-assignment */
    renderTextInput = ({ name, label, inputProps }) => (
        <FormTextInput
            {...inputProps}
            value={this.state[name].toString()}
            onChangeText={(value) => {
                this.setState({ [name]: value });
            }}
            labelText={label}
            key={name}
        />
    );
    /* eslint-enable react/destructuring-assignment */

    render() {
        const { submitBtnTitle, formFieldsRows } = this.props;

        return (
            <View>
                {/* eslint-disable react/no-array-index-key */}
                {formFieldsRows.map((formFieldsRow, i) => (
                    <View style={styles.row} key={`r-${i}`}>
                        {formFieldsRow.map(field => this.renderTextInput(field))}
                    </View>
                ))}
                {/* eslint-enable react/no-array-index-key */}

                <FormButton onPress={this.attemptFormSubmission}>{submitBtnTitle}</FormButton>
                <FormButton onPress={this.resetForm}>Reset</FormButton>
            </View>
        );
    }
}

FormBuilder.propTypes = {
    handleSubmit: PropTypes.func.isRequired,
    submitBtnTitle: PropTypes.string,
    formFieldsRows: PropTypes.arrayOf(
        PropTypes.arrayOf(
            PropTypes.shape({
                name: PropTypes.string,
                label: PropTypes.string,
                type: PropTypes.string,
                inputProps: PropTypes.object,
            }),
        ),
    ).isRequired,
};

FormBuilder.defaultProps = {
    submitBtnTitle: 'Submit',
};

const styles = StyleSheet.create({
    row: {
        flexDirection: 'row',
    },
});

export default FormBuilder;
```

As for any other components we've created before, we'll define the `propTypes` our component accepts. Two of those are required - `handleSubmit` and `formFieldsRows` (pay attention on how we require a very specific format for our configuration array), and one is optional - `submitBtnTitle` - with a default value of `Submit`.

Within our component, we'll be using some helper functions. Let's list them out and briefly described their purpose:
- `getFormFields` - because our component receives a list for `form field rows` and not just a simple list of `form fields`, we'll define this helper function which will make that conversion for us. You can see how this gets handy when we use it in our class `constructor` when we're trying to dynamically define our initial state, as well as when we need to validate our form data within our `hasValidFormData` helper function.
- `hasValidFormData` - this is a very simple implementation of form validation. We're basically checking if the user filled out all our form fields. JavaScript array method [every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) is a perfect fit for our use-case.
- `attemptFormSubmission` - a helper function which describes the action that needs to happen when the user submits the form. Here we make use of our validation function we described above, and if the validation passes, then we'll call our `handleSubmit` function passed down as a prop from `App.js`.
- `resetForm` - a helper function which describes the action that needs to happen when the user taps the `Reset` button. Basically, this will reset our state (which leads to resetting all form fields) and hide the keyboard.
- `renderTextInput` - this helper function simply renders out a `FormTextInput` component by accepting a form field object as a parameter. Note how we [unpack/destructure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Unpacking_fields_from_objects_passed_as_function_parameter) our form field object right away within the function's parameters list - `({ name, label, inputProps }) => (`. This is just a very handy ES6 feature. The main reason we moved this logic into a separate function is to keep things nice and tidy within our component's `render()` function, especially if we'll add support for other field types down the road (e.g., textarea, checkboxes etc.)

Just like that, we have defined a good list of helper functions which are being used within our component. 

I think it's worth mentioning the usage of JavaScript Array [reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) method within our `constructor` which basically loops through every single form field and produces a final object with the field name as property and an empty string as a value. That's basically our component's initial state.

```javascript
const formFieldNames = formFields.reduce((obj, field) => {
    obj[field.name] = '';
    return obj;
}, {});
```

The last piece we need to look at is the implementation of the `render()` function. The JSX content here looks pretty nice and tidy. We simply loop through our `formFieldsRows` list and render out all form inputs, as well as including the submit and reset buttons.

After all these changes, the final version of our form should look like this:

![Salary Calculator V2](https://thepracticaldev.s3.amazonaws.com/i/aoi3mtkzw4almn2gtiin.gif "Salary Calculator V2")

For a full list of changes, check out this [commit on GitHub](https://github.com/vasilestefirta/react-native-form-builder/commit/c2aa997024c5c2880077f896e8f3eb44c2cc2140).

---

Great job if you've made it this far 👏. Let's have some more fun by playing around and add some more functionality to our component. 👨‍💻😜 Do you like this idea? Then check out [Part 5](https://dev.to/dev_nope/create-a-formbuilder-component-in-react-native-part-5-535h) of this series.