react-native-stateless-form
-
Never again worry about scrolling and focusing form fields
-
Display icons and inline error messages with ease
-
Use any form state management tool you want
Screen capture
What it does
It implements the most common pattern of mobile form user interaction by convension over configuration. You'll never have to worry again about scrolling and focusing form fields.
- It uses inline form fields with icons and labels
- It displays different icons for valid and invalid field values
- It displays validation message inside the field
- When a field receives focus, it displays a keyboard (*)
- If it is not the last field in the form, the keyboard return key is set to
Next
(**) - If it is the last field in the form, the keyboard return key is set to
Done
and hides keaboard on return (**) - When a field receives focus, the form scrolls to the top of the field to avoid it being hidden behind the keyboard
- When all fields lose focus, the form scrolls back to the top of the form
(*) Unless an external keyboard is connected to the device
(**) On Android the return key button is always displayed as Done
for now, since React Native does not support changing it yet. But the behaviour works correctly ;)
What it does NOT do
- It does not implement form validation. We recommend using validate-model for that. But you can use anything you want.
- It does not implement form state management. We recommend using Redux Form for that. But you can use anything you want.
- It does not implement a submit button and enabled/disabled/loading behaviour for you. We recommend using apsl-react-native-button for that. But you can use anything you want.
Support
- React Native 0.25+
- iOS
- Android (see installation below)
Inspiration
This package is inspired by FaridSafi/react-native-gifted-form, and my intention is to merge with it in the future.
The reason for creating a new package is that I want the form components to be presentational only, and not to store state at all. This way we can easily integrate with Redux Form, any other form management tool, or even implement our own form management.
Installation
npm install react-native-stateless-form --save
Android
You should add android:windowSoftInputMode="adjustNothing"
attribute to the <activity>
tag with android:name=".MainActivity"
in your AndroidManifest.xml
. Otherwise, it will have duplicate scroll behaviour.
Examples
The dirtiest example using React state
{ superprops context thisstate = name: null email: null password: null } { const name email password = thisstate const nameValid = name && namelength > 0 ? true : false const emailValid = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i const passwordValid = password && passwordlength >= 8 ? true : false return <StatelessForm style= flex: 1 marginTop: 20 backgroundColor: 'lightgray' > <InlineTextInput label='Name' placeholder='Tell us your name' style= borderColor: 'gray' labelStyle= color: 'dimgray' inputStyle= color: 'slategray' messageStyle= color: 'red' icon= <Icon name='account-circle' size=18 color='steelblue' /> validIcon= <Icon name='check' size=18 color='green' /> invalidIcon= <Icon name='clear' size=18 color='red' /> value=name valid=nameValid message=name && !nameValid ? 'Please fill your name' : null onChangeText= { this } /> <InlineTextInput label='Email' placeholder='type@your.email' autoCorrect=false autoCapitalize='none' keyboardType='email-address' style= borderColor: 'gray' labelStyle= color: 'dimgray' inputStyle= color: 'slategray' messageStyle= color: 'red' icon= <Icon name='mail-outline' size=18 color='steelblue' /> validIcon= <Icon name='check' size=18 color='green' /> invalidIcon= <Icon name='clear' size=18 color='red' /> value=email valid=emailValid message=email && !emailValid ? 'Please enter a valid email address' : null onChangeText= { this } /> <InlineTextInput label='Password' placeholder='Create a password' autoCorrect=false autoCapitalize='none' secureTextEntry=true style= borderColor: 'gray' labelStyle= color: 'dimgray' inputStyle= color: 'slategray' messageStyle= color: 'red' icon= <Icon name='vpn-key' size=18 color='steelblue' /> validIcon= <Icon name='check' size=18 color='green' /> invalidIcon= <Icon name='clear' size=18 color='red' /> value=password valid=passwordValid message=password && !passwordValid ? 'Password too short' : null onChangeText= { this } /> </StatelessForm> } AppRegistry
Create your own component to keep it DRY
// You MUST implement focus and blur methods for your component to work { thisrefsinput } { thisrefsinput } { const iconName = thisprops return <InlineTextInput ref='input' // This is necessary for focus() and blur() implementation to work style= borderColor: 'gray' labelStyle= color: 'dimgray' inputStyle= color: 'slategray' messageStyle= color: 'red' icon= <Icon name=iconName size=18 color='steelblue' /> validIcon= <Icon name='check' size=18 color='green' /> invalidIcon= <Icon name='clear' size=18 color='red' /> ...thisprops /> } // You MUST add these two props to propTypes in order to have auto-focus and auto-scroll workingFormInputpropTypes = value: PropTypesstring valid: PropTypesbool { superprops context thisstate = name: null email: null password: null } { const name email password = thisstate const nameValid = name && namelength > 0 ? true : false const emailValid = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i const passwordValid = password && passwordlength >= 8 ? true : false return <StatelessForm style=flex: 1 marginTop: 20 backgroundColor: 'lightgray'> <FormInput label='Name' placeholder='Tell us your name' iconName='account-circle' value=name valid=nameValid message=name && !nameValid ? 'Please fill your name' : null onChangeText= { this } /> <FormInput label='Email' placeholder='type@your.email' autoCorrect=false autoCapitalize='none' keyboardType='email-address' iconName='mail-outline' value=email valid=emailValid message=email && !emailValid ? 'Please enter a valid email address' : null onChangeText= { this } /> <FormInput label='Password' placeholder='Create a password' autoCorrect=false autoCapitalize='none' secureTextEntry=true iconName='vpn-key' value=password valid=passwordValid message=password && !passwordValid ? 'Password too short' : null onChangeText= { this } /> </StatelessForm> } AppRegistry
Usage with validate-model
const UserValidators = name: title: 'Name' validate: validator: 'isLength' arguments: 1 255 email: title: 'Email' validate: validator: 'isLength' arguments: 1 255 validator: 'isEmail' message: '{TITLE} must be valid' password: title: 'Password' validate: validator: 'isLength' arguments: 8 255 message: '{TITLE} is too short' { thisrefsinput } { thisrefsinput } { const iconName name value = thisprops const valid messages = const message = messages && messageslenght > 0 ? messages0 : null return <InlineTextInput ref='input' style= borderColor: 'gray' labelStyle= color: 'dimgray' inputStyle= color: 'slategray' messageStyle= color: 'red' icon= <Icon name=iconName size=18 color='steelblue' /> validIcon= <Icon name='check' size=18 color='green' /> invalidIcon= <Icon name='clear' size=18 color='red' /> valid=valid message=message ...thisprops /> } FormInputpropTypes = value: PropTypesstring valid: PropTypesbool { superprops context thisstate = name: null email: null password: null } { const name email password = thisstate return <StatelessForm style=flex: 1 marginTop: 20 backgroundColor: 'lightgray'> <FormInput name='name' label='Name' placeholder='Tell us your name' iconName='account-circle' value=name onChangeText= { this } /> <FormInput name='email' label='Email' placeholder='type@your.email' autoCorrect=false autoCapitalize='none' keyboardType='email-address' iconName='mail-outline' value=email onChangeText= { this } /> <FormInput name='password' label='Password' placeholder='Create a password' autoCorrect=false autoCapitalize='none' secureTextEntry=true iconName='vpn-key' value=password onChangeText= { this } /> </StatelessForm> } AppRegistry
Usage with Redux Form
const UserValidators = name: title: 'Name' validate: validator: 'isLength' arguments: 1 255 email: title: 'Email' validate: validator: 'isLength' arguments: 1 255 validator: 'isEmail' message: '{TITLE} must be valid' password: title: 'Password' validate: validator: 'isLength' arguments: 8 255 message: '{TITLE} is too short' const validate = { const validation = if !validationvalid return validationmessages return {}} { thisrefsinput } { thisrefsinput } { const iconName name value error = thisprops const message = error && errorlength > 0 ? error0 : null return <InlineTextInput ref='input' style= borderColor: 'gray' labelStyle= color: 'dimgray' inputStyle= color: 'slategray' messageStyle= color: 'red' icon= <Icon name=iconName size=18 color='steelblue' /> validIcon= <Icon name='check' size=18 color='green' /> invalidIcon= <Icon name='clear' size=18 color='red' /> message=message ...thisprops /> } FormInputpropTypes = value: PropTypesstring valid: PropTypesbool { const fields: name email password = thisprops return <StatelessForm style=flex: 1 marginTop: 20 backgroundColor: 'lightgray'> <FormInput name='name' label='Name' placeholder='Tell us your name' iconName='account-circle' ...name /> <FormInput name='email' label='Email' placeholder='type@your.email' autoCorrect=false autoCapitalize='none' keyboardType='email-address' iconName='mail-outline' ...email /> <FormInput name='password' label='Password' placeholder='Create a password' autoCorrect=false autoCapitalize='none' secureTextEntry=true iconName='vpn-key' ...password /> </StatelessForm> } Form = Form; const reducers = form: formReducerconst reducer = const createStoreWithMiddleware = createStore { return }const store = const Root = <Provider store=store> <Form /> </Provider> AppRegistry
StatelessForm
A wrapper that will manage auto-focusing and auto-scrolling for its children components
Property | Type | Default | Description |
---|---|---|---|
style | style | {} | Style for the form wrapper |
+ Any other ScrollView prop you wish to pass.
Components
InlineTextInput
Property | Type | Default | Description |
---|---|---|---|
label | string | 'Use label prop' | Label for the text input |
value | string | null | Value for the text input |
valid | boolean | false | Whether the value is valid or not |
message | string | null | Validation message to be shown |
style | style | {} | Style changes to the main ScrollView |
iconStyle | style | {} | Style changes to the icon View |
labelStyle | style | {} | Style changes to the label Text |
inputStyle | style | {} | Style changes to the TextInput |
messageStyle | style | {} | Style changes to the validation message Text |
icon | element | null | Any react component to be used as icon |
validIcon | element | null | Any react component to be used as icon when valid. Requires icon prop |
invalidIcon | element | null | Any react component to be used as icon when invalid. Requires icon prop |
+ Any other TextInput prop you wish to pass.
Other components
My intention is to implement most of FaridSafi/react-native-gifted-form's components. But I'll do each one only when I need it in a real project, so it might take some time.
PR's are very much welcome!
Creating new components
Any react component can be rendered inside Stateless Form as a component. But there is a special case below:
Focusable input components
If you want your component to receive focus when previous component finished editing, you must implement the following pattern:
- Your component should implement the
focus()
method. - Your component should implement the
blur()
method. - Your component should implement
onSubmitEditing
or equivalent and callthis.props.onNextInputFocus(this.props.nextInput, this)
so StatelessForm can focus the next input or blur the current input. - Your component must have
valid
andvalue
on itspropTypes
. This is howStatelessForm
will recognize it as a focusable and/or scrollable input component. It is important that only focusable or scrollable components have these props onpropTypes
.
Scrollable input components
If you want your component to receive scroll when showing keyboard, you must implement the following pattern:
- Your component should implement
onFocus
and callthis.props.onFocus(scrollTo)
on focus.scrollTo
must be your component'sy
position. - You can get your
y
position usingonLayout
prop. Check InlineTextInput for references on how to implement it. - Your component should implement
onBlur
and callthis.props.onBlur
on blur. - Your component also must have
valid
andvalue
on itspropTypes
.
Contributing
Please create issues and send pull requests!