React Native: How to make format card expiration with / using <TextInput/>?
Asked Answered
D

4

8

In React Native using <TextInput/>, I am trying to make / appear only when the <TextInput/> is focused, and would stay there if another input is entered in. Currently, the format is MM/YY, so when the user types the third digit, it would go after the /, and if the user were to press back, it would delete the digit before the /.

So what would be the right approach to implementing previously mentioned? Thank you and will be sure to accept the answer.

I tried the following but getting an error with length, and this is only adding / after two digits have been entered:

  _changeCardExpiry(value) {
    if (value.indexOf('.') >= 0 || value.length > 5) {
      return;
    }

    if (value.length === 2 && this.state.cardExpiry.length === 1) {
      value += '/'
    }

    //then update state cardExpiry
  }

  ...

  <TextInput
    onChangeText={this._changeCardExpiry.bind(this)}
    placeholder='MM/YY'
    value={cardExpiry}
  />
Doggone answered 28/10, 2016 at 22:0 Comment(2)
What is the error with length?Siddon
@Siddon I get an error at if (text.length === 2 && this.state.cardExpiry.length === 1) saying Cannot read property 'length' of undefined.Doggone
D
5

In formattion you actually need 3 functions one is for formatting the actual value, second is for converting formatted value back to actual value and the third is needed for checking whether the entered input so far can be valid or not. For example for a date input entered letter inputs should be disregarded, but at the same time 99 should be disregarded as it is not a valid input for a month. So for your specific case the following structure should work for you (in this example inputToValue function both checks whether the entered input is valid and set state according to it):

formatFunction(cardExpiry = ""){
   //expiryDate will be in the format MMYY, so don't make it smart just format according to these requirements, if the input has less than 2 character return it otherwise append `/` character between 2nd and 3rd letter of the input.
   if(cardExpiry.length < 2){
    return cardExpiry;
   }
   else{
    return cardExpiry.substr(0, 2) + "/" + (cardExpiry.substr(2) || "")
   }
}

inputToValue(inputText){
    //if the input has more than 5 characters don't set the state
    if(inputText.length < 6){
         const tokens = inputText.split("/");
         // don't set the state if there is more than one "/" character in the given input
         if(tokens.length < 3){
            const month = Number(tokens[1]);
            const year = Number(tokens[2]);
            //don't set the state if the first two letter is not a valid month
            if(month >= 1 && month <= 12){
               let cardExpiry = month + "";
               //I used lodash for padding the month and year with  zero               
               if(month > 1 || tokens.length === 2){
                    // user entered 2 for the month so pad it automatically or entered "1/" convert it to 01 automatically
                    cardExpiry = _.padStart(month, 2, "0");   
               }
               //disregard changes for invalid years
               if(year > 1 && year <= 99){
                   cardExpiry += year;
               }
               this.setState({cardExpiry});
            }
         }
    }
}

render(){
    let {cardExpiry} = this.state;
    return <TextInput
       value = {this.formatFunction(cardExpiry)}
       onChangeText={this.inputToValue.bind(this)}/>;
}
Dominicadominical answered 3/11, 2016 at 9:43 Comment(0)
C
2

you can use this func from onChangeText;

Don't forget to bind method inside constructor ;

this.fixCardText = this.fixCardText.bind(this)

fixCardText(text){
  if(text.length == 2 && this.state.text.length == 1){
    text += '/'
  }else if(text.length == 2 && this.state.text.length == 3){
    text = text.substring(0 , text.length-1)
  }
  this.setState({text:text}) 
}

your text input should be like;

<TextInput
  value = {this.state.text}
  onChangeText={(text)=>{this.fixCardText(text)}}
/>
Colis answered 3/11, 2016 at 8:56 Comment(0)
A
2

Not complete solution, but it solves similar problem - masking bluetooth address
AB
AB:C
AB:CD:EF:GH:IJ:KL

/*
Usage:
import { TextInput } from '../utils/input'
const MaskedTextInput = withMask(TextInput)
<MaskedTextInput
  placeholder="Printer address"
  value={ printerId }
  onChange={this.handlePrinterAddressChange}
/>
*/

import React, { Component } from 'react'
import { View } from 'react-native'

export const withMask = (WrappedComponent) => class Wrapper extends Component {

  constructor(props) {
    super()
    this.state = { value: props.value }
  }

  onChange(event) {
    let value = event.nativeEvent.text
    const rawValue = event.nativeEvent.text.replace(/:/g, '')

    if (rawValue) {
      value = rawValue.match(/.{1,2}/g).join(':').toUpperCase()
    }

    this.setState({value})

    if (this.props.onChange) {
      event.nativeEvent.text = value
      this.props.onChange(event)
    }
  }

  render() {
    return <WrappedComponent 
      {...this.props} 
      onChange={(event) => this.onChange(event)}
      value={this.state.value}
    />
  }

}
Abstracted answered 6/11, 2016 at 22:57 Comment(0)
B
0

You can use 2 TextInputs instead on one to keep it simple.

here's a way of doing that.

<TextInput
            onChangeText={() => alert('update state with Expiry Month')}
            placeholder="MM/"
            value={expiryMonth}
          />
          {expiryMonth.length > 0 && <Text> / </Text>}
          <TextInput
            onChangeText={() => alert('update state with Expiry Year or do whatever you need with whole Expiry Date')}
            placeholder="YY"
            value={expiryYear}
          />
Biggers answered 22/6, 2021 at 12:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.