• Home  / 
  • Mobile
  •  /  Creating form select component in react native

Creating form select component in react native

By w s / June 15, 2017

React native comes with a Picker element out of the box. However, if you try to include this picker element immediately, it may not work really well with your iOS app. Right out of the box, Picker element for iOS will render a full height scroller. Here’s how the picker element look like on its own:

From the looks of it, we would need to somehow wrap this scroller selection in a modal dialog of its own!

Let’s create FormSelect component that can work with both Android and iOS from the get go.

First brush with Picker element

I wanted to ask the user of my app what programming language that they prefer to be writing with. The selections are limited to 7 languages:

  1. Java
  2. Javascript
  3. C#
  4. Python
  5. Ruby
  6. Go
  7. C++

A light bulb went on in my head and thought “What better element can represent selection with 1 valid value other than Picker!”

So there I went, thinking that Picker element would work equally well in both Android and iOS, so I included a Picker right after a TextInput in one of the form I was creating.

import React, { Component } from "react";
import {
AppRegistry,
Picker,
Platform,
StyleSheet,
TextInput,
View
} from "react-native";
const programmingLanguages = [
{
label: 'Java',
value: 'java',
},
{
label: 'JavaScript',
value: 'js',
},
{
label: 'Python',
value: 'python',
},
{
label: 'Ruby',
value: 'ruby',
},
{
label: 'C#',
value: 'csharp',
},
{
label: 'C++',
value: 'cpp',
},
{
label: 'C',
value: 'c',
},
{
label: 'Go',
value: 'go',
}
];
class App extends Component {
constructor(props) {
super(props);
this.state = {
text1: '',
text2: '',
language: '',
};
}
render() {
return (
<View style={styles.container}>
<View style={styles.content}>
<View style={styles.inputContainer}>
<TextInput
placeholder={'Some input'}
style={styles.input}
onChangeText={text1 => this.setState({ text1 })}
value={this.state.text1}
/>
</View>
<View style={styles.inputContainer}>
<TextInput
placeholder={'Some input 2'}
style={styles.input}
onChangeText={text2 => this.setState({ text2 })}
value={this.state.text2}
/>
</View>
<Picker
selectedValue={this.state.language}
onValueChange={itemValue => this.setState({ language: itemValue })}>
{programmingLanguages.map((i, index) => (
<Picker.Item key={index} label={i.label} value={i.value} />
))}
</Picker>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
content: {
marginLeft: 15,
marginRight: 15,
marginBottom: 5,
alignSelf: 'stretch',
justifyContent: 'center',
},
inputContainer: {
...Platform.select({
ios: {
borderBottomColor: 'gray',
borderBottomWidth: 1,
},
}),
},
input: {
height: 40
}
});
AppRegistry.registerComponent('FormPicker', () => App);
view raw picker-original.js hosted with ❤ by GitHub

When testing out the application in both Android and iOS, I realised how wrong I was.

In Android, it was quite smooth, a select box was rendered along with dropdown button on the right side of the box. Clicking on it will trigger a pop up where I can pick the value that I need.

In iOS though, all of a sudden there’s a huge tumbler smacked right after the TextInput. It is fully functional, but definitely not a good experience. So I decided to wrap the Picker component in iOS in a Modal window to ensure that the tumbler does not look as if it is out of its element.

Including picker in Android

Picker element in android works almost out of the box, it will render an inline element with pop up window to select one of the item.

To enable picker in Android, let’s create a simple FormPicker class that returns a picker element;

import React, { Component } from "react";
import { Picker } from "react-native";
class FormPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
modalVisible: false
}
}
render() {
return (
<Picker
selectedValue={this.props.value}
onValueChange={this.props.onValueChange}
>
{this.props.items.map((i, index) => (
<Picker.Item key={index} label={i.label} value={i.value} />
))}
</Picker>
);
}
}
view raw picker-android.js hosted with ❤ by GitHub

Including picker in iOS

In iOS, Picker can be trickier; especially when it comes to an inline element. We would want to render the picker in a separate modal dialog and read the updated value back to the inline element (in this case an InputText).

In order to do this, we need to create a new class that would keep the state of the modal (opened or closed) that contains the Picker and push back the value selected in the modal dialog to the parent component.

After a little bit of trial and error, the following FormPicker class would render a proper modal dialog in iOS for the select component.

import React, { Component } from "react";
import {
Picker,
Modal,
TouchableWithoutFeedback,
Text,
View,
View,
Picker,
TextInput,
Dimensions,
TouchableOpacity
} from "react-native";
class FormPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
modalVisible: false
}
}
render() {
return (
<View style={styles.inputContainer}>
<TouchableOpacity
onPress={() => this.setState({ modalVisible: true })}
>
<TextInput
style={styles.input}
editable={false}
placeholder="Select language"
onChangeText={searchString => {
this.setState({ searchString });
}}
value={this.props.value}
/>
</TouchableOpacity>
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
>
<TouchableWithoutFeedback
onPress={() => this.setState({ modalVisible: false })}
>
<View style={styles.modalContainer}>
<View style={styles.buttonContainer}>
<Text
style={{ color: "blue" }}
onPress={() => this.setState({ modalVisible: false })}
>
Done
</Text>
</View>
<View>
<Picker
selectedValue={this.props.value}
onValueChange={this.props.onValueChange}
>
{this.props.items.map((i, index) => (
<Picker.Item
key={index}
label={i.label}
value={i.value}
/>
))}
</Picker>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
</View>
);
}
};
const styles = StyleSheet.create({
inputContainer: {
...Platform.select({
ios: {
borderBottomColor: "gray",
borderBottomWidth: 1
}
})
},
input: {
height: 40
},
modalContainer: {
flex: 1,
justifyContent: "flex-end"
},
buttonContainer: {
justifyContent: "flex-end",
flexDirection: "row",
padding: 4,
backgroundColor: "#ececec"
}
});
view raw picker-ios.js hosted with ❤ by GitHub

Getting one entry point

In order to ease the usage of the Picker element, we can wrap both Android and iOS pickers in one class. In order to do that, we can repurpose the FormPicker classes we created earlier to cater for both systems.

The FormPicker class would then need to be injected with:

  1. The available values for the picker
  2. Currently selected value of the picker
  3. Function to invoke when an element in the picker is selected to update the parent’s form value
  4. Placeholder text when no value is selected yet

With this needs in mind, we can then write the FormPicker element like so:

import React, { Component } from "react";
import {
Modal,
TouchableWithoutFeedback,
Text,
StyleSheet,
Platform,
View,
Picker,
TextInput,
TouchableOpacity,
AppRegistry
} from "react-native";
const programmingLanguages = [
{
label: "Java",
value: "java"
},
{
label: "JavaScript",
value: "js"
},
{
label: "Python",
value: "python"
},
{
label: "Ruby",
value: "ruby"
},
{
label: "C#",
value: "csharp"
},
{
label: "C++",
value: "cpp"
},
{
label: "C",
value: "c"
},
{
label: "Go",
value: "go"
}
];
class FormPicker extends Component {
constructor(props) {
super(props);
this.state = {
modalVisible: false
};
}
render() {
if (Platform.OS === "android") {
return (
<Picker
selectedValue={this.props.value}
onValueChange={this.props.onValueChange}
>
{this.props.items.map((i, index) => (
<Picker.Item key={index} label={i.label} value={i.value} />
))}
</Picker>
);
} else {
const selectedItem = this.props.items.find(
i => i.value === this.props.value
);
const selectedLabel = selectedItem ? selectedItem.label : "";
return (
<View style={styles.inputContainer}>
<TouchableOpacity
onPress={() => this.setState({ modalVisible: true })}
>
<TextInput
style={styles.input}
editable={false}
placeholder="Select language"
onChangeText={searchString => {
this.setState({ searchString });
}}
value={selectedLabel}
/>
</TouchableOpacity>
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
>
<TouchableWithoutFeedback
onPress={() => this.setState({ modalVisible: false })}
>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<Text
style={{ color: "blue" }}
onPress={() => this.setState({ modalVisible: false })}
>
Done
</Text>
</View>
<View
onStartShouldSetResponder={evt => true}
onResponderReject={evt => {}}
>
<Picker
selectedValue={this.props.value}
onValueChange={this.props.onValueChange}
>
{this.props.items.map((i, index) => (
<Picker.Item
key={index}
label={i.label}
value={i.value}
/>
))}
</Picker>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
</View>
);
}
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
text1: "",
text2: "",
language: ""
};
}
render() {
return (
<View style={styles.container}>
<View style={styles.content}>
<View style={styles.inputContainer}>
<TextInput
placeholder={"Some input"}
style={styles.input}
onChangeText={text1 => this.setState({ text1 })}
value={this.state.text1}
/>
</View>
<View style={styles.inputContainer}>
<TextInput
placeholder={"Some input 2"}
style={styles.input}
onChangeText={text2 => this.setState({ text2 })}
value={this.state.text2}
/>
</View>
<FormPicker
items={programmingLanguages}
value={this.state.language}
onValueChange={(itemValue, itemIndex) =>
this.setState({ language: itemValue })}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center"
},
content: {
marginLeft: 15,
marginRight: 15,
marginBottom: 5,
alignSelf: "stretch",
justifyContent: "center"
},
inputContainer: {
...Platform.select({
ios: {
borderBottomColor: "gray",
borderBottomWidth: 1
}
})
},
input: {
height: 40
},
modalContainer: {
flex: 1,
justifyContent: "flex-end"
},
modalContent: {
justifyContent: "flex-end",
flexDirection: "row",
padding: 4,
backgroundColor: "#ececec"
}
});
AppRegistry.registerComponent('FormPicker', () => App);
view raw picker-complete.js hosted with ❤ by GitHub

Here’s the complete app in action through application hosted in exponent.

What do you guys think? Any better way to deal with Picker element in both iOS and Android ( aside from libraries 😉 )? Let me know in the comment!

  • Tan Tiong Hour

    Hi, the dropdown picker tutorial is good but how I choose the first value in the picker? because I want to choose the Javascript then just can choose the Java in the picker. Thank you

    • Wira Siwananda

      Hi, that can be achieved by setting initial state. So the language in state started of with a value.
      For example:
      class App extends Component {
      constructor(props) {
      super(props);
      this.state = {
      text1: ”,
      text2: ”,
      language: ‘js’,
      };
      }

      }
      You can find the example in this expo snack: https://snack.expo.io/BkSzO42tb

      • Tan Tiong Hour

        I got try follow your example before but it is still didn’t show any default value was selected as shown in the figure below:

        constructor(props){
        super(props);
        this.state = {
        avatarSource: null,
        data: null,
        category_route: 1,
        building: 19,
        created_by: 138
        } https://uploads.disquscdn.com/images/733d6e48e5b11b806cded506f17398cf50bedfb2291c334a8cd433f77d10ce94.png

        I have three pickers value need to be selected first but at here it doesn’t shows any value. Please help, thank you.

        • Wira Siwananda

          Do you mind creating a github gist that renders the pickers with the list of elements for the picker and the selected value? As I can’t see the issue from the screenshot

          • Tan Tiong Hour

            Hi, please find the NewTodo.js in the app/component there in this Github: https://github.com/JohnHour89/FMS, thank you

          • Wira Siwananda

            Looking at the code, I think I found the issue.

            In your item listing, you have the value as string. In your state, you’re declaring an integer. So the value does not match.
            You can change the default value in your state to string, like so:
            constructor(props){
            super(props);
            this.state = {
            avatarSource: null,
            data: null,
            category_route: “1”,
            building: “19”,
            created_by: “138”
            }

            Or update all the value in your item listing to integer.

          • Tan Tiong Hour

            Wow!!! It works like a charm!!! Thank you so much~~~

          • Wira Siwananda

            no problem 🙂

          • Tan Tiong Hour

            I want it look like this which have the default selected value then also can choose the other values: https://github.com/theredfoxfire/react-native-dropdown-modal

          • Tan Tiong Hour

            yeah, it seems like no have the issue but if want to choose the first value I need to choose the second value just then can choose the first value like I want to choose “Others” option but I need to choose “Air Con” option then just can choose the “Others” option. It looks like not user friendly.

  • Tan Tiong Hour

    Hi, Sir, may I know how to disable the picker if I just want to display the selected value only when in Edit form? Thank you

    • Tan Tiong Hour

      huh, I just change this to false then ok already

      this.setState({ modalVisible: false })}>

      • Wira Siwananda

        Great that it works out for you!

  • Mariam Abdussalam

    Is there a way to set the font size of the picker items?