{"id":18063290,"url":"https://github.com/danielweinmann/react-native-stateless-form","last_synced_at":"2025-04-11T15:40:18.807Z","repository":{"id":57340376,"uuid":"52241053","full_name":"danielweinmann/react-native-stateless-form","owner":"danielweinmann","description":"Stateless form components for React Native","archived":false,"fork":false,"pushed_at":"2017-12-19T17:22:04.000Z","size":4685,"stargazers_count":105,"open_issues_count":4,"forks_count":14,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-25T11:49:20.222Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielweinmann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-22T02:25:37.000Z","updated_at":"2024-07-17T05:59:48.000Z","dependencies_parsed_at":"2022-08-29T16:22:04.346Z","dependency_job_id":null,"html_url":"https://github.com/danielweinmann/react-native-stateless-form","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielweinmann%2Freact-native-stateless-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielweinmann%2Freact-native-stateless-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielweinmann%2Freact-native-stateless-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielweinmann%2Freact-native-stateless-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielweinmann","download_url":"https://codeload.github.com/danielweinmann/react-native-stateless-form/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248432861,"owners_count":21102456,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-31T05:10:31.156Z","updated_at":"2025-04-11T15:40:18.771Z","avatar_url":"https://github.com/danielweinmann.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-native-stateless-form\n\n* \u003ch4\u003eNever again worry about scrolling and focusing form fields\u003c/h4\u003e\n* \u003ch4\u003eDisplay icons and inline error messages with ease\u003c/h4\u003e\n* \u003ch4\u003eUse any form state management tool you want\u003c/h4\u003e\n\n## Screen capture\n\n![](https://raw.githubusercontent.com/danielweinmann/react-native-stateless-form/master/StatelessForm.gif)\n\n## What it does\n\nIt 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.\n\n- It uses inline form fields with icons and labels\n- It displays different icons for valid and invalid field values\n- It displays validation message inside the field\n- When a field receives focus, it displays a keyboard (\\*)\n- If it is not the last field in the form, the keyboard return key is set to `Next`\n- If it is the last field in the form, the keyboard return key is set to `Done` and hides keaboard on return\n- When a field receives focus, the form scrolls to the top of the field to avoid it being hidden behind the keyboard\n- When all fields lose focus, the form scrolls back to the top of the form\n\n(\\*) Unless an external keyboard is connected to the device\n\n## What it does NOT do\n\n- It does not implement form validation. We recommend using [validate-model](https://github.com/danielweinmann/validate-model) for that. But you can use anything you want.\n- It does not implement form state management. We recommend using [Redux Form](http://erikras.github.io/redux-form/) for that. But you can use anything you want.\n- It does not implement a submit button and enabled/disabled/loading behaviour for you. We recommend using [apsl-react-native-button](https://github.com/APSL/react-native-button) for that. But you can use anything you want.\n\n## Support\n\n- React Native 0.25+\n- iOS\n- Android (see installation below)\n\n## Inspiration\n\nThis package is inspired by [FaridSafi/react-native-gifted-form](https://github.com/FaridSafi/react-native-gifted-form), and my intention is to merge with it in the future.\n\nThe 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.\n\n## Installation\n\n```npm install react-native-stateless-form --save```\n\n#### Android\n\nYou should add `android:windowSoftInputMode=\"adjustNothing\"` attribute to the `\u003cactivity\u003e` tag with `android:name=\".MainActivity\"` in your `AndroidManifest.xml`. Otherwise, it will have duplicate scroll behaviour.\n\n## Examples\n\n#### The dirtiest example using React state\n\n```js\nimport React, { Component } from 'react-native'\nimport Icon from 'react-native-vector-icons/MaterialIcons'\nimport { StatelessForm, InlineTextInput } from 'react-native-stateless-form'\n\nclass Form extends Component {\n  constructor(props, context) {\n    super(props, context)\n    this.state = {\n      name: null,\n      email: null,\n      password: null,\n    }\n  }\n\n  render() {\n    const { name, email, password } = this.state\n    const nameValid = (name \u0026\u0026 name.length \u003e 0 ? true : false)\n    const emailValid = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i.test(email)\n    const passwordValid = (password \u0026\u0026 password.length \u003e= 8 ? true : false)\n    return (\n      \u003cStatelessForm style={{\n        flex: 1,\n        marginTop: 20,\n        backgroundColor: 'lightgray',\n      }}\u003e\n        \u003cInlineTextInput\n          label='Name'\n          placeholder='Tell us your name'\n          style={{ borderColor: 'gray' }}\n          labelStyle={{ color: 'dimgray' }}\n          inputStyle={{ color: 'slategray' }}\n          messageStyle={{ color: 'red' }}\n          icon={ \u003cIcon name={'account-circle'} size={18} color={'steelblue'} /\u003e }\n          validIcon={ \u003cIcon name='check' size={18} color='green' /\u003e }\n          invalidIcon={ \u003cIcon name='clear' size={18} color='red' /\u003e }\n          value={name}\n          valid={nameValid}\n          message={name \u0026\u0026 !nameValid ? 'Please fill your name' : null}\n          onChangeText={(text) =\u003e { this.setState({name: text}) }}\n        /\u003e\n        \u003cInlineTextInput\n          label='Email'\n          placeholder='type@your.email'\n          autoCorrect={false}\n          autoCapitalize='none'\n          keyboardType='email-address'\n          style={{ borderColor: 'gray' }}\n          labelStyle={{ color: 'dimgray' }}\n          inputStyle={{ color: 'slategray' }}\n          messageStyle={{ color: 'red' }}\n          icon={ \u003cIcon name={'mail-outline'} size={18} color={'steelblue'} /\u003e }\n          validIcon={ \u003cIcon name='check' size={18} color='green' /\u003e }\n          invalidIcon={ \u003cIcon name='clear' size={18} color='red' /\u003e }\n          value={email}\n          valid={emailValid}\n          message={email \u0026\u0026 !emailValid ? 'Please enter a valid email address' : null}\n          onChangeText={(text) =\u003e { this.setState({email: text}) }}\n        /\u003e\n        \u003cInlineTextInput\n          label='Password'\n          placeholder='Create a password'\n          autoCorrect={false}\n          autoCapitalize='none'\n          secureTextEntry={true}\n          style={{ borderColor: 'gray' }}\n          labelStyle={{ color: 'dimgray' }}\n          inputStyle={{ color: 'slategray' }}\n          messageStyle={{ color: 'red' }}\n          icon={ \u003cIcon name={'vpn-key'} size={18} color={'steelblue'} /\u003e }\n          validIcon={ \u003cIcon name='check' size={18} color='green' /\u003e }\n          invalidIcon={ \u003cIcon name='clear' size={18} color='red' /\u003e }\n          value={password}\n          valid={passwordValid}\n          message={password \u0026\u0026 !passwordValid ? 'Password too short' : null}\n          onChangeText={(text) =\u003e { this.setState({password: text}) }}\n        /\u003e\n      \u003c/StatelessForm\u003e\n    )\n  }\n}\n\nimport { AppRegistry } from 'react-native'\nAppRegistry.registerComponent('Form', () =\u003e Form)\n```\n\n#### Create your own component to keep it DRY\n\n```js\nimport React, { Component } from 'react-native'\nimport PropTypes from 'prop-types'\nimport Icon from 'react-native-vector-icons/MaterialIcons'\nimport { StatelessForm, InlineTextInput } from 'react-native-stateless-form'\n\nclass FormInput extends Component {\n  // You MUST implement focus and blur methods for your component to work\n  focus() {\n    this.refs.input.focus()\n  }\n\n  blur() {\n    this.refs.input.blur()\n  }\n\n  render() {\n    const { iconName } = this.props\n    return (\n      \u003cInlineTextInput\n        ref='input' // This is necessary for focus() and blur() implementation to work\n        style={{ borderColor: 'gray' }}\n        labelStyle={{ color: 'dimgray' }}\n        inputStyle={{ color: 'slategray' }}\n        messageStyle={{ color: 'red' }}\n        icon={ \u003cIcon name={iconName} size={18} color={'steelblue'} /\u003e }\n        validIcon={ \u003cIcon name='check' size={18} color='green' /\u003e }\n        invalidIcon={ \u003cIcon name='clear' size={18} color='red' /\u003e }\n        { ...this.props }\n      /\u003e\n    )\n  }\n}\n\n// You MUST add these two props to propTypes in order to have auto-focus and auto-scroll working\nFormInput.propTypes = {\n  value: PropTypes.string,\n  valid: PropTypes.bool,\n}\n\nclass Form extends Component {\n  constructor(props, context) {\n    super(props, context)\n    this.state = {\n      name: null,\n      email: null,\n      password: null,\n    }\n  }\n\n  render() {\n    const { name, email, password } = this.state\n    const nameValid = (name \u0026\u0026 name.length \u003e 0 ? true : false)\n    const emailValid = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i.test(email)\n    const passwordValid = (password \u0026\u0026 password.length \u003e= 8 ? true : false)\n    return (\n      \u003cStatelessForm style={{flex: 1, marginTop: 20, backgroundColor: 'lightgray'}}\u003e\n        \u003cFormInput\n          label='Name'\n          placeholder='Tell us your name'\n          iconName='account-circle'\n          value={name}\n          valid={nameValid}\n          message={name \u0026\u0026 !nameValid ? 'Please fill your name' : null}\n          onChangeText={(text) =\u003e { this.setState({name: text}) }}\n        /\u003e\n        \u003cFormInput\n          label='Email'\n          placeholder='type@your.email'\n          autoCorrect={false}\n          autoCapitalize='none'\n          keyboardType='email-address'\n          iconName='mail-outline'\n          value={email}\n          valid={emailValid}\n          message={email \u0026\u0026 !emailValid ? 'Please enter a valid email address' : null}\n          onChangeText={(text) =\u003e { this.setState({email: text}) }}\n        /\u003e\n        \u003cFormInput\n          label='Password'\n          placeholder='Create a password'\n          autoCorrect={false}\n          autoCapitalize='none'\n          secureTextEntry={true}\n          iconName='vpn-key'\n          value={password}\n          valid={passwordValid}\n          message={password \u0026\u0026 !passwordValid ? 'Password too short' : null}\n          onChangeText={(text) =\u003e { this.setState({password: text}) }}\n        /\u003e\n      \u003c/StatelessForm\u003e\n    )\n  }\n}\n\nimport { AppRegistry } from 'react-native'\nAppRegistry.registerComponent('Form', () =\u003e Form)\n```\n\n#### Usage with validate-model\n\n```js\nimport React, { Component } from 'react-native'\nimport PropTypes from 'prop-types'\nimport Icon from 'react-native-vector-icons/MaterialIcons'\nimport { StatelessForm, InlineTextInput } from 'react-native-stateless-form'\nimport { validate } from 'validate-model'\n\nconst UserValidators = {\n  name: {\n    title: 'Name',\n    validate: [{\n      validator: 'isLength',\n      arguments: [1, 255],\n    }]\n  },\n  email: {\n    title: 'Email',\n    validate: [{\n      validator: 'isLength',\n      arguments: [1, 255],\n    },\n    {\n      validator: 'isEmail',\n      message: '{TITLE} must be valid',\n    }]\n  },\n  password: {\n    title: 'Password',\n    validate: [{\n      validator: 'isLength',\n      arguments: [8, 255],\n      message: '{TITLE} is too short',\n    }]\n  },\n}\n\nclass FormInput extends Component {\n  focus() {\n    this.refs.input.focus()\n  }\n\n  blur() {\n    this.refs.input.blur()\n  }\n\n  render() {\n    const { iconName, name, value } = this.props\n    const { valid, messages } = validate(UserValidators[name], value)\n    const message = (messages \u0026\u0026 messages.lenght \u003e 0 ? messages[0] : null)\n    return (\n      \u003cInlineTextInput\n        ref='input'\n        style={{ borderColor: 'gray' }}\n        labelStyle={{ color: 'dimgray' }}\n        inputStyle={{ color: 'slategray' }}\n        messageStyle={{ color: 'red' }}\n        icon={ \u003cIcon name={iconName} size={18} color={'steelblue'} /\u003e }\n        validIcon={ \u003cIcon name='check' size={18} color='green' /\u003e }\n        invalidIcon={ \u003cIcon name='clear' size={18} color='red' /\u003e }\n        valid={valid}\n        message={message}\n        { ...this.props }\n      /\u003e\n    )\n  }\n}\n\nFormInput.propTypes = {\n  value: PropTypes.string,\n  valid: PropTypes.bool,\n}\n\nclass Form extends Component {\n  constructor(props, context) {\n    super(props, context)\n    this.state = {\n      name: null,\n      email: null,\n      password: null,\n    }\n  }\n\n  render() {\n    const { name, email, password } = this.state\n    return (\n      \u003cStatelessForm style={{flex: 1, marginTop: 20, backgroundColor: 'lightgray'}}\u003e\n        \u003cFormInput\n          name='name'\n          label='Name'\n          placeholder='Tell us your name'\n          iconName='account-circle'\n          value={name}\n          onChangeText={(text) =\u003e { this.setState({name: text}) }}\n        /\u003e\n        \u003cFormInput\n          name='email'\n          label='Email'\n          placeholder='type@your.email'\n          autoCorrect={false}\n          autoCapitalize='none'\n          keyboardType='email-address'\n          iconName='mail-outline'\n          value={email}\n          onChangeText={(text) =\u003e { this.setState({email: text}) }}\n        /\u003e\n        \u003cFormInput\n          name='password'\n          label='Password'\n          placeholder='Create a password'\n          autoCorrect={false}\n          autoCapitalize='none'\n          secureTextEntry={true}\n          iconName='vpn-key'\n          value={password}\n          onChangeText={(text) =\u003e { this.setState({password: text}) }}\n        /\u003e\n      \u003c/StatelessForm\u003e\n    )\n  }\n}\n\nimport { AppRegistry } from 'react-native'\nAppRegistry.registerComponent('Form', () =\u003e Form)\n```\n\n#### Usage with Redux Form\n\n```js\nimport React, { Component } from 'react-native'\nimport PropTypes from 'prop-types'\nimport Icon from 'react-native-vector-icons/MaterialIcons'\nimport { StatelessForm, InlineTextInput } from 'react-native-stateless-form'\nimport { validateAll } from 'validate-model'\nimport { Provider } from 'react-redux'\nimport { createStore, combineReducers, applyMiddleware } from 'redux'\nimport { reduxForm, reducer as formReducer } from 'redux-form'\nimport createLogger from 'redux-logger'\n\nconst UserValidators = {\n  name: {\n    title: 'Name',\n    validate: [{\n      validator: 'isLength',\n      arguments: [1, 255],\n    }]\n  },\n  email: {\n    title: 'Email',\n    validate: [{\n      validator: 'isLength',\n      arguments: [1, 255],\n    },\n    {\n      validator: 'isEmail',\n      message: '{TITLE} must be valid',\n    }]\n  },\n  password: {\n    title: 'Password',\n    validate: [{\n      validator: 'isLength',\n      arguments: [8, 255],\n      message: '{TITLE} is too short',\n    }]\n  },\n}\n\nconst validate = values =\u003e {\n  const validation = validateAll(UserValidators, values)\n  if (!validation.valid) return validation.messages\n  return {}\n}\n\nclass FormInput extends Component {\n  focus() {\n    this.refs.input.focus()\n  }\n\n  blur() {\n    this.refs.input.blur()\n  }\n\n  render() {\n    const { iconName, name, value, error } = this.props\n    const message = ( error \u0026\u0026 error.length \u003e 0 ? error[0] : null)\n    return (\n      \u003cInlineTextInput\n        ref='input'\n        style={{ borderColor: 'gray' }}\n        labelStyle={{ color: 'dimgray' }}\n        inputStyle={{ color: 'slategray' }}\n        messageStyle={{ color: 'red' }}\n        icon={ \u003cIcon name={iconName} size={18} color={'steelblue'} /\u003e }\n        validIcon={ \u003cIcon name='check' size={18} color='green' /\u003e }\n        invalidIcon={ \u003cIcon name='clear' size={18} color='red' /\u003e }\n        message={message}\n        { ...this.props }\n      /\u003e\n    )\n  }\n}\n\nFormInput.propTypes = {\n  value: PropTypes.string,\n  valid: PropTypes.bool,\n}\n\nclass Form extends Component {\n  render() {\n    const { fields: { name, email, password } } = this.props\n    return (\n      \u003cStatelessForm style={{flex: 1, marginTop: 20, backgroundColor: 'lightgray'}}\u003e\n        \u003cFormInput\n          name='name'\n          label='Name'\n          placeholder='Tell us your name'\n          iconName='account-circle'\n          { ...name }\n        /\u003e\n        \u003cFormInput\n          name='email'\n          label='Email'\n          placeholder='type@your.email'\n          autoCorrect={false}\n          autoCapitalize='none'\n          keyboardType='email-address'\n          iconName='mail-outline'\n          { ...email }\n        /\u003e\n        \u003cFormInput\n          name='password'\n          label='Password'\n          placeholder='Create a password'\n          autoCorrect={false}\n          autoCapitalize='none'\n          secureTextEntry={true}\n          iconName='vpn-key'\n          { ...password }\n        /\u003e\n      \u003c/StatelessForm\u003e\n    )\n  }\n}\n\nForm = reduxForm({\n  form: 'user',\n  fields: ['name', 'email', 'password'],\n  validate\n})(Form);\n\nconst reducers = {\n  form: formReducer\n}\nconst reducer = combineReducers(reducers)\nconst createStoreWithMiddleware = applyMiddleware(createLogger())(createStore)\nfunction configureStore(initialState) {\n  return createStoreWithMiddleware(reducer, initialState)\n}\nconst store = configureStore()\n\nconst Root = () =\u003e (\n  \u003cProvider store={store}\u003e\n    \u003cForm /\u003e\n  \u003c/Provider\u003e\n)\n\nimport { AppRegistry } from 'react-native'\nAppRegistry.registerComponent('Form', () =\u003e Root)\n```\n\n## StatelessForm\n\nA wrapper that will manage auto-focusing and auto-scrolling for its children components\n\n| Property | Type | Default | Description |\n|---------------|----------|--------------|----------------------------------------------------------------|\n| style | style | {} | Style for the form wrapper |\n\n\\+ Any other [ScrollView](https://facebook.github.io/react-native/docs/scrollview.html#content) prop you wish to pass.\n\n## Components\n\n#### InlineTextInput\n\n| Property | Type | Default | Description |\n|---------------|----------|--------------|----------------------------------------------------------------|\n| label | string | 'Use label prop' | Label for the text input |\n| value | string | null | Value for the text input |\n| valid | boolean | false | Whether the value is valid or not |\n| message | string | null | Validation message to be shown |\n| style | style | {} | Style changes to the main ScrollView |\n| iconStyle | style | {} | Style changes to the icon View |\n| labelStyle | style | {} | Style changes to the label Text |\n| inputStyle | style | {} | Style changes to the TextInput |\n| messageStyle | style | {} | Style changes to the validation message Text |\n| icon | element | null | Any react component to be used as icon |\n| validIcon | element | null | Any react component to be used as icon when valid. Requires `icon` prop |\n| invalidIcon | element | null | Any react component to be used as icon when invalid. Requires `icon` prop |\n\n\\+ Any other [TextInput](https://facebook.github.io/react-native/docs/textinput.html#content) prop you wish to pass.\n\n#### Other components\n\nMy intention is to implement most of [FaridSafi/react-native-gifted-form](https://github.com/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.\n\nPR's are very much welcome!\n\n## Creating new components\n\nAny react component can be rendered inside Stateless Form as a component. But there is a special case below:\n\n#### Focusable input components\n\nIf you want your component to receive focus when previous component finished editing, you must implement the following pattern:\n\n- Your component should implement the `focus()` method.\n- Your component should implement the `blur()` method.\n- Your component should implement `onSubmitEditing` or equivalent and call `this.props.onNextInputFocus(this.props.nextInput, this)` so StatelessForm can focus the next input or blur the current input.\n- Your component must have `valid` and `value` on its `propTypes`. This is how `StatelessForm` will recognize it as a focusable and/or scrollable input component. It is important that only focusable or scrollable components have these props on `propTypes`.\n\n#### Scrollable input components\n\nIf you want your component to receive scroll when showing keyboard, you must implement the following pattern:\n\n- Your component should implement `onFocus` and call `this.props.onFocus(scrollTo)` on focus. `scrollTo` must be your component's `y` position.\n- You can get your `y` position using `onLayout` prop. Check [InlineTextInput](https://github.com/danielweinmann/react-native-stateless-form/blob/master/components/InlineTextInput.js) for references on how to implement it.\n- Your component should implement `onBlur` and call `this.props.onBlur` on blur.\n- Your component also must have `valid` and `value` on its `propTypes`.\n\n## Contributing\n\nPlease create issues and send pull requests!\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielweinmann%2Freact-native-stateless-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielweinmann%2Freact-native-stateless-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielweinmann%2Freact-native-stateless-form/lists"}