This series contents:
- Part 1: Create a new React Native app
- Part 2: Create a simple Salary Calculator Form
- Part 3: Create custom form input and button components (current)
- Part 4: Work on the
FormBuilder
component - Part 5: Enable/disable form buttons on-the-fly
- Part 6: Create a Sign Up form
- Part 7: Add support for Boolean field type
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
andchildren
. BecauseonPress
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 extendsReact.Component
. Again, this is just a preference. In fact, the other custom component we'll talk about in a second (FormTextInput
) was defined as aclass
, 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 theFormTextInput
component, and spread them into the React Native'sTextInput
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 defaultTextInput
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 thelabelText
, 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 iflabelText
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)