DEV Community

Cover image for Create a FormBuilder component in React Native (Part 3)

Create a FormBuilder component in React Native (Part 3)

This series contents:

Part 3: Create custom form input and button components

When creating React Native apps you'll always run into situations where you want to use the same UI element in multiple places. Often that element will look and behave in a specific way. It's not really a good practice to customize a component over and over again in different places, so in order to avoid any code duplication, it's recommended to split your app screen into multiple little components, which can be re-used anywhere across your app later on if needed.

Create a custom FormButton component

Let's take our form buttons as an example. We have two of them and they both:

  • accept a title
  • accept a callback function which is triggered when the button is tapped
  • have the same styles.

We can easily define a custom FormButton component which will satisfy all these needs and will look like this:

import React from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';

/**
 * A stateless function component which renders a button.
 *
 * @param {obj} props
 */
const FormButton = (props) => {
    const { children, onPress } = props;

    return (
        <TouchableOpacity style={styles.button} onPress={onPress}>
            <Text style={styles.buttonText}>{children}</Text>
        </TouchableOpacity>
    );
};

FormButton.propTypes = {
    onPress: PropTypes.func,
    children: PropTypes.string.isRequired,
};

FormButton.defaultProps = {
    onPress: f => f,
};

const styles = StyleSheet.create({
    button: {
        backgroundColor: '#FD6592',
        borderRadius: 3,
        height: 40,
        marginBottom: 15,
        justifyContent: 'center',
        alignItems: 'center',
    },
    buttonText: {
        color: '#FFF',
        fontWeight: 'bold',
        fontSize: 16,
    },
});

export default FormButton;

Here are some pinpoints I want to mention in regards to creating this new component:

  • we've moved the button styles from our App.js file into this new component;
  • we're using the prop-types library to document the intended types of properties passed to our component. For more information about how to use PropTypes, check out the React's Typechecking With PropTypes documentation. Please note that you're not required to use PropTypes while building your app. I just personally think it's a really good way to document your components and it can definitely be handy for both you or any other developer that'll have to work with your code sometime down the road. As you can see our component accepts two props: onPress and children. Because onPress is not a required prop, we need to define a default value for it, which in our case is a function that does "nothing" (or to be more specific, it's an arrow function with an implicit return);
  • because our component doesn't do anything other than rendering something to the user, I've defined it as a stateless function instead of being class that extends React.Component. Again, this is just a preference. In fact, the other custom component we'll talk about in a second (FormTextInput) was defined as a class, just to demonstrate that both approaches can be used.

Now we can re-use this custom button anywhere in our app like so:

// ...
import FormButton from 'path/to/FormButton';
// ...
render() {
    // ...
    return (
        <FormButton onPress={() => { console.log('Button pressed!'); }}>
            Press Me!
        </FormButton>
    );
}

Create a custom FormTextInput component

The same way we created a custom component for our form buttons, we can create one for our form text inputs. That way if we'll have multiple screens with some forms that require text inputs, we can easily re-use our custom text inputs components. Let's create a new component and call it FormTextInput, which will look like this:

import React from 'react';
import PropTypes from 'prop-types';
import {
    View, TextInput, Text, StyleSheet,
} from 'react-native';

/**
 * A component which renders a TextInput with a label above it.
 * Note: This component can easily be written as a stateless function
 *      since it only includes the `render()` function and nothing else
 *      (see FormButton component as an example).
 */
class FormTextInput extends React.Component {
    render() {
        const { labelText, ...inputProps } = this.props;

        return (
            <View style={styles.inputWrapper}>
                {labelText && <Text style={styles.label}>{labelText}</Text>}
                <TextInput style={styles.textInput} blurOnSubmit {...inputProps} />
            </View>
        );
    }
}

FormTextInput.propTypes = {
    labelText: PropTypes.string,
};

FormTextInput.defaultProps = {
    labelText: null,
};

const styles = StyleSheet.create({
    inputWrapper: {
        marginBottom: 15,
        flexDirection: 'column',
    },
    textInput: {
        height: 40,
        borderColor: '#FFF',
        borderWidth: 1,
        borderRadius: 3,
        backgroundColor: '#FFF',
        paddingHorizontal: 10,
        fontSize: 18,
        color: '#3F4EA5',
    },
    label: {
        color: '#FFF',
        marginBottom: 5,
    },
});

export default FormTextInput;

Here are some pinpoints I want to mention in regards to creating this new component:

  • we've moved the text inputs styles from our App.js file into this new component;

  • we're making use of JavaScript rest and spread operators to collect all default TextInput's props (e.g. placeholder, keyboardType, etc.) we're passing down to the FormTextInput component, and spread them into the React Native's TextInput component. Pay attention to these 2 lines of code:

// Using the "rest" operator.
// You can read the following line like this:
// Hey, give me the "labelText" property's value from "this.props" object 
// and store it into a "labelText" variable. Then, take all of the remaining
// properties from "this.props" and store them into a "inputProps" variable 
// (which ends up being an object).
const { labelText, ...inputProps } = this.props;

// Using the "spread" operator
// Now we can simply take our "inputProps" object which contains
// all of the props we wanted to pass down to the "TextInput" component,
// and spread them out into it.
// (same as writing: "<TextInput placeholder={inputProps.placeholder}" />
<TextInput style={styles.textInput} blurOnSubmit {...inputProps} />
  • we're also using the prop-types library to document the intended types of properties passed to our component. Please note that we're not documenting any of the default TextInput component's props. That's because we don't really have control over them and React Native itself is taking care of that. The only prop we're documenting here is the labelText, which is an optional custom prop we're using to display a label above the text input. Check out the way we're checking in JSX if labelText has a value or not:
{labelText && <Text style={styles.label}>{labelText}</Text>}

You can read this line like this: if the labelText happens to be a "truthy" value then render out the Text component.

Update our App.js file

Now it's the time to update our main App.js file to use these newly created components. The updated version should look like this:

import React, { Component } from 'react';
import {
    StyleSheet, KeyboardAvoidingView, Text, Keyboard, Alert,
} from 'react-native';

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

export default class App extends Component {
    // ...
    render() {
        // ...
        return (
            <KeyboardAvoidingView behavior="padding" style={styles.container}>
                <Text style={styles.screenTitle}>Salary Calculator</Text>
                <FormTextInput
                    placeholder="$0"
                    keyboardType="numeric"
                    returnKeyType="done"
                    onChangeText={text => this.setState({ hourlyRate: text })}
                    value={hourlyRate}
                    labelText="Hourly Rate"
                />
                <FormTextInput
                    placeholder="0"
                    keyboardType="numeric"
                    returnKeyType="done"
                    onChangeText={text => this.setState({ hoursPerWeek: text })}
                    value={hoursPerWeek}
                />
                <FormButton onPress={this.handleSubmit}>Calculate</FormButton>
                <FormButton onPress={this.resetForm}>Reset</FormButton>
            </KeyboardAvoidingView>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        paddingHorizontal: 20,
        backgroundColor: '#3F4EA5',
    },
    screenTitle: {
        fontSize: 35,
        textAlign: 'center',
        margin: 10,
        color: '#FFF',
    },
});

For a full list of changes, check out this commit on GitHub.


Great job if you've made it this far πŸ‘. Now it's the time to move to Part 4, where we'll start working on a custom FormBuilder component which would render out a fully functional form. πŸ‘‰

Top comments (0)