sanniassin / react-input-mask

Input masking component for React. Made with attention to UX.
MIT License
2.22k stars 258 forks source link

Using two dynamic masks #190

Closed alvarogonoring closed 4 years ago

alvarogonoring commented 4 years ago

Anyone knows how to use two masks, this way: The first mask is "999.999.999-99", when it reachs his max length, it becomes "99.999.999/9999-99".

Thanks!

raphaelpc commented 4 years ago

Hello! Hope this implementation i made for a Brazilian phone field can help you to elaborate your solution. It also uses Formik eand react-bootstrap.

const PhoneInput = props => {

    const { name, placeholder, setFieldValue, value } = props;

    // Remove formatting and re-format after.
    // Necessary to resolve bugs with formatting and auto-complete
    const ajusta = v => {
        const digitos = !v ? '' : v.replace(/[^\d]/g, '');
        if (!digitos || digitos.length < 10) return v;
        const corte = digitos.length === 10 ? 6 : 7;
        const max = digitos.length > 11 ? 11 : digitos.length;
        return `(${digitos.substring(0, 2)}) ${digitos.substring(2, corte)}-${digitos.substring(corte, max)}`;
    }

    const maskBuilder = v => {
        if (!v || v.length == 0) return '';
        const a = ajusta(v);
        return (a.length >= 6 && a[5] === '9') ? '(99) 99999-9999' : '(99) 9999-9999';
    }

    const handleChange = e => {
        setFieldValue(name, ajusta(e.target.value));
    }

    return (
            <InputMask
                name={name}
                value={value}
                onChange={handleChange}
                mask={maskBuilder(value)}
                placeholder={placeholder}
                maskPlaceholder={null}
            >
                <BootstrapForm.Control type="text" />
            </InputMask>
    )
}
valterson commented 4 years ago

Hello! Hope this implementation i made for a Brazilian phone field can help you to elaborate your solution. It also uses Formik eand react-bootstrap.

const PhoneInput = props => {

    const { name, placeholder, setFieldValue, value } = props;

    // Remove formatting and re-format after.
    // Necessary to resolve bugs with formatting and auto-complete
    const ajusta = v => {
        const digitos = !v ? '' : v.replace(/[^\d]/g, '');
        if (!digitos || digitos.length < 10) return v;
        const corte = digitos.length === 10 ? 6 : 7;
        const max = digitos.length > 11 ? 11 : digitos.length;
        return `(${digitos.substring(0, 2)}) ${digitos.substring(2, corte)}-${digitos.substring(corte, max)}`;
    }

    const maskBuilder = v => {
        if (!v || v.length == 0) return '';
        const a = ajusta(v);
        return (a.length >= 6 && a[5] === '9') ? '(99) 99999-9999' : '(99) 9999-9999';
    }

    const handleChange = e => {
        setFieldValue(name, ajusta(e.target.value));
    }

    return (
            <InputMask
                name={name}
                value={value}
                onChange={handleChange}
                mask={maskBuilder(value)}
                placeholder={placeholder}
                maskPlaceholder={null}
            >
                <BootstrapForm.Control type="text" />
            </InputMask>
    )
}

NICE @raphaelpc it helped me a lot thank you.

ed1nh0 commented 3 years ago

Anyone knows how to use two masks, this way: The first mask is "999.999.999-99", when it reachs his max length, it becomes "99.999.999/9999-99".

Thanks!

I made this and worked fine. No extra functions.

const [value, setValue] = useState('');

<InputMask
    mask={value.length < 15 ? "999.999.999-999" : "99.999.999/0001-99"}
    maskChar=""
    value={value}
    onChange={(e) => setValue(e.target.value)}
/>

How this works? The first mask should be "999.999.999-99" but with the extra "9" at the end the mask changes. Notice the maskChar with empty ("") value. This is important for not set the field's length before the user fills it.

vitorpedeo commented 3 years ago

Anyone knows how to use two masks, this way: The first mask is "999.999.999-99", when it reachs his max length, it becomes "99.999.999/9999-99". Thanks!

I made this and worked fine. No extra functions.

const [value, setValue] = useState('');

<InputMask
    mask={value.length < 15 ? "999.999.999-999" : "99.999.999/0001-99"}
    maskChar=""
    value={value}
    onChange={(e) => setValue(e.target.value)}
/>

How this works? The first mask should be "999.999.999-99" but with the extra "9" at the end the mask changes. Notice the maskChar with empty ("") value. This is important for not set the field's length before the user fills it.

This solution works if you just type in the characters. But it won't work properly if you try to paste a value.

EriJohnson commented 3 years ago

I made this implementation to handle Brazilian phone numbers. But it is necessary to use this function in a normal "input" instead of "InputMask". This function must be passed in the "onKeyPress" of the input. It worked fine for me, and also works with copy and paste commands.

function validatePhoneInput(event) {
  let { value } = event.target

  value = value.replace(/\D/g, '')
  value = value.replace(/(\d{2})/, '($1) ')
  const isMobilePhone = /^[(][0-9][0-9][)][\s][9]/.test(value)

  if (!/[0-9]/.test(event.key)) {
    event.preventDefault()
  }

  if (isMobilePhone) {
    event.currentTarget.maxLength = 16
    value = value.replace(/\D/g, '')
    value = value.replace(/(\d{2})(\d{1})/, '($1) $2.')
    value = value.replace(/(\d{4})/, '$1-')
    value = value.replace(/(\d{4})/, '$1')
  } else {
    event.currentTarget.maxLength = 14
    value = value.replace(/(\d{4})/, '$1-')
    value = value.replace(/(\d{4})/, '$1')
  }

  event.currentTarget.value = value
}

export default validatePhoneInput

Example of application :

<input type='tel' onKeyPress={HandlePhoneMask} />
ed1nh0 commented 3 years ago

I made this implementation to handle Brazilian phone numbers. But it is necessary to use this function in a normal "input" instead of "InputMask". This function must be passed in the "onKeyPress" of the input. It worked fine for me, and also works with copy and paste commands.

Looking forward to see a similar solution to mask a CPF/CNPJ input. Thank you in advance!

JorgeKunrath commented 3 years ago

I've merged both @raphaelpc and @ed1nh0 answers:

return (a.length >= 6 && a[5] === '9') ? '(99) 99999-9999' : '(99) 9999-9999';

mask={value.length < 15 ? "999.999.999-999" : "99.999.999/0001-99"} maskChar=""

And came up with this that solves my problem:

// Component with custom stuff and Formik `Field` input, with `InputMask` inside
<Input
    label="Celular com DDD"
    type="tel"
    name="whatsapp"
    mask={(value) => (value?.length >= 6 && value?.[5] === "9" ? "(99) 99999-9999" : "(99) 9999-9999")}
    maskChar=""
/>

// ---
// Input component
// if props.maks:
<Field name={name}>
    {(field) => (
        <InputMask
            mask={props.mask(field.form.values[name])}
            maskChar={props.maskChar}
            onChange={field.field.onChange}
            name={name}
            type={type}
        />
    )}
</Field>

Vlw gente! 🤙

bruno-silva5 commented 2 years ago

Based in the @ed1nh0 's solution, this is what I did:

dynamicMask() {
    if (!this.state.cpfCnpjValue || this.state.cpfCnpjValue.length === 0) 
        return "999999999999999999";

    if (this.state.cpfCnpjValue.replace(/\D/g, "").length < 12) {
        return "999.999.999-999999";
     } else {
        return "99.999.999/9999-99";
    }
}

<InputMask 
    mask={this.dynamicMask()} 
    autoFocus name='identity' 
    maskChar={''}
    className="form-control" 
    onChange={e => this.setState({ cpfCnpjValue: e.currentTarget.value })} 
/> 

Copy & paste works fine now!

annerland commented 2 years ago

Based in the @ed1nh0 's solution, this is what I did:

dynamicMask() {
    if (!this.state.cpfCnpjValue || this.state.cpfCnpjValue.length === 0) 
        return "999999999999999999";

    if (this.state.cpfCnpjValue.replace(/\D/g, "").length < 12) {
        return "999.999.999-999999";
     } else {
        return "99.999.999/9999-99";
    }
}

<InputMask 
    mask={this.dynamicMask()} 
    autoFocus name='identity' 
    maskChar={''}
    className="form-control" 
    onChange={e => this.setState({ cpfCnpjValue: e.currentTarget.value })} 
/> 

Copy & paste works fine now!

I did exactly like you, thanks for that <3

(also be careful with the placeholder, i was stucked bc of that, so always clean the mask placeholder)

MarlonEngFront commented 1 year ago

tenho um campo de input que tem que definir a mascara de acordo com o dado informado, ou seja são 4 tipos de dados para 1 input, são eles = cpf, telefone, email e username, já utilizei varias formas, mas não achei nada que me atenda, alguem tem alguma ideia, isso no Brasil => CPF = 000.000.000.00 / Telefone = (00) 00000-0000 / email e username ?

ed1nh0 commented 1 year ago

tenho um campo de input que tem que definir a mascara de acordo com o dado informado, ou seja são 4 tipos de dados para 1 input, são eles = cpf, telefone, email e username, já utilizei varias formas, mas não achei nada que me atenda, alguem tem alguma ideia, isso no Brasil => CPF = 000.000.000.00 / Telefone = (00) 00000-0000 / email e username ?

Complicado. CPF de CNPJ a máscara muda por conta da quantidade de dígitos (11 e 14 respectivamente, desconsiderando pontos, barra e traço). Telefone (celular) também tem 11 dígitos, você só conseguiria discernir do CPF por conta do algoritmo de validação dele. Nome e e-mail bastaria ter um @ para que não seja nome. Enfim, uma solução pro seu caso é complexa.

MarlonEngFront commented 1 year ago

sim, pensei em validar o cpf antes de aplicar a mascara, mas dai teria que ver quando essa validação entraria em vigor, levando em conta o onchange e o onblur do react, e tambem ter uma validação do numero de telefone do Brasil, ou seja, esta bem complicado, estou procurando soluçoes, para isso..

ed1nh0 commented 1 year ago

sim, pensei em validar o cpf antes de aplicar a mascara, mas dai teria que ver quando essa validação entraria em vigor, levando em conta o onchange e o onblur do react, e tambem ter uma validação do numero de telefone do Brasil, ou seja, esta bem complicado, estou procurando soluçoes, para isso..

Você precisa escolher com que máscara quer iniciar. No caso de ainda haver NOME e E-MAIL junto (onde deveria haver apenas dígitos, leia-se números), fica bastante difícil, mas creio que seja possível. No exemplo dado começo com uma máscara de CPF e, caso a quantidade de dígitos passe de 11 a máscara muda para CNPJ, daí o nome de dynamicMask() para a função.

Eu tentaria manter um campo específico para CPF/CNPJ e outro para NOME/E-MAIL. Se isso realmente não for possível no seu projeto, eu tentaria da seguinte forma: se o primeiro caractere digitado no campo for uma letra, comece sem máscara nenhuma (isso daria a entender que se trata de um NOME) e, caso em algum momento o "@" seja digitado, você já implementa uma validação de E-MAIL. Caso o primeiro caractere seja um número, comece com a de CPF.

MarlonEngFront commented 1 year ago

entendi sua logica, a implementação tem que ser somente um input mesmo, pra atender esses 4 casos, vou fazer uns testes em código pra ver se consigo..valew.

MarlonEngFront commented 1 year ago

@ed1nh0 segue o repositorio com a implementação com multiplos inputs - https://github.com/MarlonEngFront/DynamicInputMask