npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-native-advanced-forms

v1.1.6

Published

Flexible React Native architecture for building and managing forms

Downloads

45

Readme

react-native-advanced-forms

npm Join the chat at https://discord.gg/bYt4tWB Follow on Twitter

Demo1

Flexible React Native architecture for building and managing forms.

Features:

  • Cross-platform (iOS, Android)
  • Allow for flexible form layouts
  • Easily manage and validate field input values
  • Auto-focussing of next field for better user experience
  • Easily integrate your own custom components
  • Component-based - use with or without Redux
  • Compatible with React Native 0.40+
  • Successfully used in production apps

Installation

Use NPM/Yarn to install package: react-native-advanced-forms

Note: You must also have the prop-types package installed.

npm install

Demo

A working demo app can be found in the demo folder (Note that you will need to copy src to demo/src for the app to successfully build).

Usage

This code will render a form similar to what can be seen in the demo animation above:

import React from 'react'
import { Button, Alert, StyleSheet, Text, View } from 'react-native'
import Form from 'react-native-advanced-forms'

export default class App extends React.Component {
  constructor (props, ctx) {
    super(props, ctx)

    this.state = {
      firstName: null,
      lastName: null,
      age: null,
      country: null,
    }
  }

  render() {
    const {
      firstName, lastName, age, country
    } = this.state

    return (
      <View style={styles.container}>
        <Form ref={this._onFormRef} onChange={this.onChange} onSubmit={this.onSubmit} validate={this.validate}>
          <Form.Layout style={styles.row}>
            <Form.Layout style={styles.columns}>
              <Form.Field name="firstName" label="First name" style={styles.field}>
                <Form.TextField value={firstName} />
              </Form.Field>
              <Form.Field name="lastName" label="Last name" style={styles.field}>
                <Form.TextField value={lastName} />
              </Form.Field>
            </Form.Layout>
          </Form.Layout>
          <Form.Layout style={styles.row}>
            <Form.Field name="age" label="Age" style={styles.ageField}>
              <Form.TextField value={age} keyboardType='numeric'/>
            </Form.Field>
          </Form.Layout>
          <Form.Layout style={styles.row}>
            <Form.Field name="country" label="Country" style={styles.field}>
              <Form.TextField value={country} />
            </Form.Field>
          </Form.Layout>
        </Form>
        <View style={styles.button}>
          <Button
            disabled={this.form && !this.form.canSubmit()}
            onPress={() => this.form.validateAndSubmit()}
            title="Submit"
            color="#841584"
          />
        </View>
      </View>
    )
  }

  _onFormRef = e => {
    this.form = e
  }

  onChange = (values) => {
    this.setState(values)
  }

  onSubmit = (values) => {
    Alert.alert('Submitted: ' + JSON.stringify(values))
  }

  validate = (values) => {
    const ret = Object.keys(this.state).reduce((m, v) => {
      if (!values[v] || !values[v].length) {
        m[v] = Form.VALIDATION_RESULT.MISSING
      }
      return m
    }, {})

    if (!ret.age && isNaN(values.age)) {
      ret.age = Form.VALIDATION_RESULT.INCORRECT
    }

    return ret
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingTop: 100,
    paddingHorizontal: 30
  },
  row: {
    marginBottom: 20,
  },
  columns: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start',
  },
  field: {
    marginRight: 10,
  },
  ageField: {
    width: 60,
  },
  button: {
    width: 80,
    marginTop: 15,
  },
  error: {
    marginTop: 10,
  },
  errorMsg: {
    color: 'red'
  }
})

## API and props

Form

This is the root component for a form and is responsible for co-ordinating form value changes and auto-focussing components.

Properties:

| Prop | Type | Default | Description | | --------- | --------- | --------- | --------- | | submitOnReturn | boolean | true | If turned off then the form will NOT be auto-submitted when the user presses the Return key after filling in the final field | | onChange | function (Object values) | required | Called whenever form values change | | onSubmit | function (Object values) | required | Called form values have passed validation and form is to be submitted | | validate | function (Object values) | required | Called to validate form values. Must return an Object mapping field name to validation error. If it returns {} then it means all fields are valid. | | onValidationError | function (Array badFieldNames) | null | Called when validation fails. | | onFocusField | function (String fieldName, Function callback) | null | Called when form is about to auto-focus a field. The callback must be invoked for focussing to proceed. | | style | Any | null | Styling to apply to form container element. |

Methods:

| Method | Returns | Description | | --------- | --------- | --------- | | validateAndSubmit | undefined | Validate the form field values and submit it if validation succeeds. | | validate | undefined | Validate the form field values and highlight errors if any. Returns true if validation passed. | | unfocus | undefined | Unfocus all form fields. | | getValues | Object | Get current form field values. | | canSubmit | boolean | Get whether current form field values are valid such that form can be submitted. |

Form.Layout

This component works similarly to React Native View and is to be used to create whatever type of layout your require for your form. Form.Layout instances can be nested within each other to multiple levels without issue.

Properties:

| Prop | Type | Default | Description | | --------- | --------- | --------- | --------- | | style | Any | null | Styling to apply to form container element. |

Form.Section

This is a convenience component which constructs a Form.Layout to wrap its children but additionally attaches text above it:

section

Properties:

| Prop | Type | Default | Description | | --------- | --------- | --------- | --------- | | title | String | null | The title text to show. | | style | Any | null | Style to apply to root container. | | layoutStyle | Any | null | Style to apply to nested Form.Layout. | | titleTextStyle | Any | null | Style to apply to title text, is shown. |

Form.Field

This component must wrap every actual input element. It is responsible for setting up onChange and onSubmit handlers as well as pass through focus/unfocus and error display commands from the parent form.

If a label gets passed in it will use Form.LabelGroup and Form.Label to render a label above the wrapped form field component.

| Prop | Type | Default | Description | | --------- | --------- | --------- | --------- | | name | String | required | The field name. | | label | String | null | The field label. | | labelStyle | Any | null | The field label container style. | | labelTextStyle | Any | null | The field label text style. | | labelRightContent | Any | null | What to show in the right-hand side of the field label container (using Form.LabelGroup). | | style | Any | null | The style for the root container. | | onSubmit | Function | null | Usually set by the parent Form. | | onChange | Function | null | Usually set by the parent Form. |

Form.TextField

A text field designed to work well with a Form. It handles validation error display and scroll view integration.

| Prop | Type | Default | Description | | --------- | --------- | --------- | --------- | | value | Any | null | The field value. | | error | Any | null | The current field validation error. If set then the field is in "error" mode. | | style | Any | null | The style for the text field in non-error mode. | | errorStyle | Any | null | The style for the text field in error mode. | | getParentScrollView | Function | null | Function to get reference to scroll view further up the UI hierarchy (if any). This is used to auto-scroll the scroll view such that the text field is visible whenever it receives focus. | | onSubmit | Function | null | Usually set by the parent Form.Field. | | onChange | Function | null | Usually set by the parent Form.Field. |

Methods:

| Method | Returns | Description | | --------- | --------- | --------- | | focus | undefined | Focus this field (should show the keyboard). | | unfocus | undefined | Unfocus this field (should hide the keyboard). | | getValue | Any | Get current field value (will return this.props.values). |

Form.Label

Text to display above a form field element as its label. Gets automatically rendered by Form.Field if a label has been set.

| Prop | Type | Default | Description | | --------- | --------- | --------- | --------- | | style | Any | null | The style for the root container. | | textStyle | Any | null | The style for the label text. |

Form.LabelGroup

A helper component which wraps a Form.Label, allowing for additional components to be displayed alongside it.

Flexible layouts

The Form.Layout component is key to achieving flexible layouts. By default, if you don't use Form.Layout then components will be stacked on top of each other (assuming default flexDirection for the Form):

<Form onChange={this.onChange} onSubmit={this.onSubmit} validate={this.validate}>
  <Form.Field name="firstName">
    <Form.TextField value={firstName} />
  </Form.Field>
  <Form.Field name="lastName">
    <Form.TextField value={lastName} />
  </Form.Field>
  <Form.Field name="age">
    <Form.TextField value={age} />
  </Form.Field>
</Form>

If you wish to place the first two components next to each other then simply wrap them within a Form.Layout with the appropriate styling:

<Form onChange={this.onChange} onSubmit={this.onSubmit} validate={this.validate}>

  <Form.Layout style={{ flexDirection: 'row' }}>
    <Form.Field name="firstName">
      <Form.TextField value={firstName} />
    </Form.Field>
    <Form.Field name="lastName">
      <Form.TextField value={lastName} />
    </Form.Field>
  </Form.Layout>

  <Form.Field name="age">
    <Form.TextField value={age} />
  </Form.Field>

</Form>

And you can nest layouts:

<Form onChange={this.onChange} onSubmit={this.onSubmit} validate={this.validate}>

  <Form.Layout style={{ marginBottom: 10 }}>
    <Form.Layout style={{ flexDirection: 'row' }}>
      <Form.Field name="firstName">
        <Form.TextField value={firstName} />
      </Form.Field>
      <Form.Field name="lastName">
        <Form.TextField value={lastName} />
      </Form.Field>
    </Form.Layout>
  </Form.Layout>

  <Form.Layout style={{ marginBottom: 10 }}>
    <Form.Field name="age">
      <Form.TextField value={age} />
    </Form.Field>
  </Form.Layout>

</Form>

Note: A working example of the above can be found in the demo folder.

Form validation and submission

You must provide a validate property to the form, the value of which is a function which returns which fields have failed validation, for example:

render () {
  return (
    <Form onSubmit={this.onSubmit} validate={this.validate} ...>
      ...
    </Form>
  )
}

validate = (values) => {
  const ret = {}

  if (!values.firstName) {
    ret.firstName = Form.VALIDATION_RESULT.MISSING
  }

  if (isNaN(values.age)) {
    ret.age = Form.VALIDATION_RESULT.INCORRECT
  }

  return ret
}

onSubmit = (values) = {
  ...
}

Note: The default VALIDATION_RESULT values can be found in src/utils.js:

If all fields have valid values then validate() must return {}. If validation thus succeeds the form will be submitted and the onSubmit handler which you passed in will be called with the values.

If you try submit the form programmatically (by calling validateAndSubmit()) and some fields have not yet been filled in, then they will be highlighted. On the other hand, if you submit a field (i.e. you enter text and then press done or the equivalent on your keyboard) then the form logic will auto-focus on the next field that needs to be filled in:

Demo2

Note: Form.Field components will pass an error object down to their children (the actual input elements) if a validation error occurs.

Custom components

You can integrate your own custom form component into a Form, as long as you follow certain rules:

  1. Your component must expose focus(), unfocus() and getValue() methods, just like as Form.TextField does.
  2. Your component must call this.props.onChange() when its value changes.
  3. Your component must call this.props.onSubmit() when it gets "submitted", e.g. for text fields the user may be press Done on the keyboard to submit the input.

Let's say we wish to integrate the React Native Switch component. Here is how we might define it:

import { Switch } from 'react-native'

class CustomSwitch extends Component {
  render () {
    const { turnedOn } = this.props

    return (
      <Switch
        value={turnedOn}
        onValueChange={this.onPress}
      />
    )
  }

  onPress = () => {
    this.props.onChange(!this.props.turnedOn)
  }

  getValue () {
    return this.props.turnedOn
  }

  // still need these methods even if they're empty
  focus () { }
  unfocus () {}
}

In our form code we would then use it as such:

const { name, isMale } = this.state

<Form onChange={this.onChange} onSubmit={this.onSubmit} validate={this.validate}>
  <Form.Field name="name">
    <Form.TextField value={name} />
  </Form.Field>
  <Form.Field name="isMale">
    <CustomSwitch turnedOn={isMale} />
  </Form.Field>
</Form>

Your component will receive an error prop containing a validation error whenever validation fails. It is up to you if/when you make use of this.

Note: There is a working example of a custom component (a dropdown using react-native-modal-filter-picker) in the demo folder.

Auto-scrolling to fields (ScrollView)

Sometimes you will need to place your form within a React Native ScrollView because it is longer than the height of the screen. In such cases the problem with auto-focussing fields is that the focussed field may not be visible in the current scroll area.

Thus, when a field receives focus it must be able to tell the ScrollView to scroll to it such that it's visible. The Form.TextField component already does this, as long as you pass in the getParentScrollView prop:

<ScrollView ref={e => { this.scrollView = e; }}>
  <Form onChange={this.onChange} onSubmit={this.onSubmit} validate={this.validate}>

    <Form.Layout>{ /* other stuff here */ }</Form.Layout>

    <Form.Field name="name">
      <Form.TextField value={name} getParentScrollView={() => this.scrollView} />
    </Form.Field>

    <Form.Layout>{ /* other stuff here */ }</Form.Layout>

    <Form.Field name="age">
      <Form.TextField value={age} getParentScrollView={() => this.scrollView} />
    </Form.Field>

    <Form.Layout>{ /* other stuff here */ }</Form.Layout>

  </Form>
</ScrollView>

Internally, Form.TextField uses Form.utils.scrollToComponentInScrollView() to actually perform the scroll. When building your own form component you can use this method too to achieve the same effect.

License

MIT