简体   繁体   中英

JavaScript replace only executes on complete match of the regex when using groups

I'm trying to create an input mask with the following pattern: "4.01.05.01-6". In the onchange method I execute a function that uses the replace method:

value = value
.replace(/\D/g, '')
.replace(
/^(\d{1})(\d{2})(\d{2})(\d{2})(\d{1})/,
'$1.$2.$3.$4-$5'
)

The problem is that I only receive the masked input value after the regex is fully matched, when I have the 8 digits. That's not what I want. I'd like to type in the input and the mask will be working while I'm typing, for example, if I just typed "1234" I'd like to have the value "1.23.4"

OBS: I don't the following info matters, but I'm using React with a controlled Input, this variable called "value" is used to make the setState.

Make all parts of the regex but the first one optional using an optional non-capturing group. Then, you can check if each optional group matched in a callback function used as the replacement argument, and build the replacement string dynamically.

See the JavaScript demo:

 function App() { const [Version, setVersion] = React.useState('') function maskVersion({ target: { value } }) { const re = /^(\d)(?:(\d{1,2})(?:(\d{1,2})(?:(\d{1,2})(\d)?)?)?)?[\w\W]*$/ setVersion(value.replace(/\D/g,'').replace(re, (_,a,b,c,d,e) => a + ( b? `.${b}`: "") + ( c? `.${c}`: "") + ( d? `.${d}`: "") + ( e? `-${e}`: "") )) } return <div> Text: <input type='text' onChange={maskVersion} value={Version} /> </div> } ReactDOM.render(<App/>, document.querySelector('#root'))
 <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>

The regex - see its online demo - matches:

  • ^ - start of string
  • (\d) - Capturing group 1 ( a ): one digit
  • (?:(\d{1,2})(?:(\d{1,2})(?:(\d{1,2})(\d)?)?)?)? - an optional non-capturing group matching
    • (\d{1,2}) - Capturing group 2 ( b ): one or two digits
    • (?:(\d{1,2})(?:(\d{1,2})(\d)?)?)? - an optional non-capturing group matching
      • (\d{1,2}) - Capturing group 3 ( c ): one or two digits
      • (?:(\d{1,2})(\d)?)? - an optional non-capturing group matching
        • (\d{1,2}) - Capturing group 4 ( d ): one or two digits
        • (\d)? - an optional capturing group 5 ( e ): on digit
  • [\w\W]* - any zero or more chars (to truncate the input string if more chars are typed)
  • $ - end of string.

The replacement is built using the ternary operator, if b is matched, a dot plus b value is appended, if c or d is matched, the same happens, and if there is e group match, a - and the e group value is added.

I can't think of any way doing it with regex only. I think you should make cases with the length of the input so when the input is, lets say, 3 digits long, then run the replace for these digits only like this

if(value.length >= 2 && value.length <= 3){
value.replace(
/^(\d{1})(\d{1,2})/,
'$1.$2'
)
}

Do the same for cases of digit length 4 - 5 etc.

So the whole code could be like this

if(value.length >= 2 && value.length <= 3){
value.replace(
/^(\d{1})(\d{1,2})/,
'$1.$2'
)
}else if(value.length >= 4 && value.length <= 5){
value.replace(
/^(\d{1})(\d{2})(\d{1,2})/,
'$1.$2.$3'
)
}else if(value.length >= 6 && value.length <= 7){
value.replace(
/^(\d{1})(\d{2})(\d{2})(\d{1,2})/,
'$1.$2.$3.$4'
)
}else if(value.length == 8){
value.replace(
/^(\d{1})(\d{2})(\d{2})(\d{2})(\d{1})/,
'$1.$2.$3.$4-$5'
)
}

Maybe i have some errors in the above code but i think you got the idea

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM