Updated September 20, 2017
Easily Build Forms in React Native
Originally publish on medium.com.
If you’re building a React Native app it’s almost a guarantee that you’ll have to build at least one form. The reality is you’ll probably build quite a few (sign in, sign up, edit profile, etc.)
Forms are pretty simple, right?
Yeah! But there can be quite a few moving parts involved. A few things you might have to take care of…
- Creating the necessary components (text inputs, select inputs, checkboxes, switches, etc)
- Writing/finding validation logic
- Updating your components to display any errors (I always forget to do this)
- Updating your UI whenever you change fields
None of these things are difficult, but they can be time consuming. Especially if you’re trying to ship something quickly.
In the past I’ve always done all of this myself. But it takes time away from what I really want to be doing.
I started looking around for solutions to solve this problem and came across tcomb-form-native. It makes creating forms in React Native easy by simply having to define a model for that form and you’re set! No fussing around with a dozen components.
Setup
To get started I’ve put together a Snack that will allow us to do all of our work on the web. It’s an awesome tool.
You’ll notice at the top of the file I’ve added import t from 'tcomb-form-native';
which is the library we’ll be using to build our form. Snack will automatically pull that in for us.
Tip: I find that scanning the QR code from the Expo app on my device gives the best development experience.
If you prefer to do this outside of Snack make sure you install tcomb-form-native
via npm or yarn.
What We’re Building
We’ll be building a sign up form that has the following fields
- Email (required)
- Username (optional)
- Password (required)
- Agree to terms (required)
Defining the Form Model
The first thing we need to do is define a model for our form — this is what will be passed to the form component and used to render the fields.
When defining the model we need to specify at minimum the field and the type of value that it accepts.
SignUp.js
import t from 'tcomb-form-native';
const User = t.struct({
email: t.String,
username: t.String,
password: t.String,
terms: t.Boolean,
});
We’re just using a few of the different types but there are more available.
Let’s go ahead and actually render this form to see what we’ve got. First we need to get the Form component (line 6) then we need to render the form and pass our model to it as the type (line 19).
SignUp.js
import React, { Component } from 'react';
import { View, StyleSheet } from 'react-native';
import t from 'tcomb-form-native'; // 0.6.9
const Form = t.form.Form;
const User = t.struct({
email: t.String,
username: t.String,
password: t.String,
terms: t.Boolean,
});
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<Form type={User} /> {/* Notice the addition of the Form component */}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
marginTop: 50,
padding: 20,
backgroundColor: '#ffffff',
},
});
Capturing Form Data
Good start! Now let’s actually try to capture that data. We’ll just use the basic Button
component (which comes from react-native) to do this and add a handleSubmit
function to the component.
SignUp.js
export default class App extends Component {
handleSubmit = () => {
// do the things
};
render() {
return (
<View style={styles.container}>
<Form type={User} />
<Button title="Sign Up!" onPress={this.handleSubmit} />
</View>
);
}
}
Now to actually access and work with our form we need to add a reference to it and with that we should be able to call getValue()
from handleSubmit
.
SignUp.js
export default class App extends Component {
handleSubmit = () => {
const value = this._form.getValue(); // use that ref to get the form value
console.log('value: ', value);
};
render() {
return (
<View style={styles.container}>
<Form
ref={(c) => (this._form = c)} // assign a ref
type={User}
/>
<Button title="Sign Up!" onPress={this.handleSubmit} />
</View>
);
}
}
However when you press “Sign Up” all the fields go red — automatic validation!
That’s coming from when we defined our model earlier. However, the username should be an optional field. Let’s fix that.
Customizing the Form
To do that we’ll use t.maybe
to tell tcomb it’s an optional field.
SignUp.js
const User = t.struct({
email: t.String,
username: t.maybe(t.String),
password: t.String,
terms: t.Boolean,
});
Boom. That easy.
I also want to change the label for the “Terms” field. I want it to say “Agree to Terms” — we can do this via options passed to the Form
.
This part is a bit verbose but it makes sense the way it’s set up. We create an object, add a key named “fields” (since we’re modifying the fields), add a key to this object that aligns with the key in our model (“terms”), and then inside that object we set the new label.
Code is easier to understand.
SignUp.js
const User = t.struct({ ... });
const options = {
fields: {
terms: {
label: 'Agree to Terms',
},
},
};
Then pass those options to the Form
component
SignUp.js
export default class App extends Component {
...
render() {
return (
<View style={styles.container}>
<Form
ref={c => this._form = c}
type={User}
options={options} // pass the options via props
/>
<Button
title="Sign Up!"
onPress={this.handleSubmit}
/>
</View>
);
}
}
Let’s do the same thing to add some error messages.
SignUp.js
const options = {
fields: {
email: {
error:
'Without an email address how are you going to reset your password when you forget it?',
},
password: {
error:
"Choose something you use on a dozen other sites or something you won't remember",
},
terms: {
label: 'Agree to Terms',
},
},
};
Modifying the Form Design
The final thing I want to do is modify the design of this form slightly. You can do quite a few things but I’m just going to be changing the label color on this one form.
The first thing I’m going to do is create a new formStyles
object and copy over all of the default styles so I don’t have to start from scratch
SignUp.js
const formStyles = {
...Form.stylesheet,
};
Here’s the full default stylesheet.
Then I’ll customize it with my A+ design skillz.
SignUp.js
const formStyles = {
...Form.stylesheet,
controlLabel: {
normal: {
color: 'blue',
fontSize: 18,
marginBottom: 7,
fontWeight: '600',
},
error: {
color: 'red',
fontSize: 18,
marginBottom: 7,
fontWeight: '600',
},
},
};
Simple & beautiful design modifications. Just about everything in the default form is customizable.
As you can see tcomb-form-native
makes building forms quick and easy but also gives you the flexibility you need to create forms the give users the necessary info and fit with the rest of your app (unless I’m designing of course).
You can get the final code on Snack.
And that’s all, folks! I’ve just scratched the surface of what this package is capable of. I encourage you to try it out in your apps! (And make sure to thank the package author — open source ain’t easy!)