【问题标题】:React - How to format phone number as user typesReact - 如何将电话号码格式化为用户类型
【发布时间】:2019-09-23 01:52:19
【问题描述】:

我需要将 10 位字符串格式化为以下格式:'(123) 456-7890'。 但是,我需要在用户键入时发生这种情况。因此,如果用户仅输入了 3 位数字,则输入应显示:'(123)'。 如果他们输入了 5 位数字,则输入应显示:'(123) 45'

使用我当前的代码,格式仅在输入第 10 个字符后进行。我希望它从第三个字符开始格式化。

const phoneRegex = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/

const handleInput = (value) => {
  return (
    value.replace(phoneRegex, '($1) $2-$3')
  )
}

class FindASubscriber extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      value: ''
    }
  }

  render() {
    const { label, placeholder, feedback } = this.props
    const { value} = this.state
    return (
      <div className="find__a__subscriber">
        <FlexGrid>
          <FlexGrid.Col>
            <FlexGrid.Row>
              <Input
                feedback={feedback}
                label={label}
                type="text"
                pattern="[0-9]*"
                placeholder={placeholder}
                value={handleInput(value)}
                maxLength="10"
                onChange={
                 (event) => this.setState({value: event.target.value})
                }
              />
            </FlexGrid.Row>
          </FlexGrid.Col>
        </FlexGrid>
      </div>
    )
  }
}```

【问题讨论】:

    标签: javascript regex reactjs validation formatting


    【解决方案1】:

    你可以像这样normalizeinput

    • value 相对于event.target.value 是最新的
    • previousValue 已经过验证并设置为 state

    这是为了防止无效字符更新输入,并将输入限制为 10 个数字。

    点击下面的Run code snippet 按钮查看工作示例。


    const normalizeInput = (value, previousValue) => {
      // return nothing if no value
      if (!value) return value; 
    
      // only allows 0-9 inputs
      const currentValue = value.replace(/[^\d]/g, '');
      const cvLength = currentValue.length; 
    
      if (!previousValue || value.length > previousValue.length) {
    
        // returns: "x", "xx", "xxx"
        if (cvLength < 4) return currentValue; 
    
        // returns: "(xxx)", "(xxx) x", "(xxx) xx", "(xxx) xxx",
        if (cvLength < 7) return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`; 
    
        // returns: "(xxx) xxx-", (xxx) xxx-x", "(xxx) xxx-xx", "(xxx) xxx-xxx", "(xxx) xxx-xxxx"
        return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3, 6)}-${currentValue.slice(6, 10)}`; 
      }
    };
    

    const normalizeInput = (value, previousValue) => {
      if (!value) return value;
      const currentValue = value.replace(/[^\d]/g, '');
      const cvLength = currentValue.length;
      
      if (!previousValue || value.length > previousValue.length) {
        if (cvLength < 4) return currentValue;
        if (cvLength < 7) return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`;
        return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3, 6)}-${currentValue.slice(6, 10)}`;
      }
    };
    
    const validateInput = value => {
      let error = ""
      
      if (!value) error = "Required!"
      else if (value.length !== 14) error = "Invalid phone format. ex: (555) 555-5555";
      
      return error;
    };
        
    class Form extends React.Component {
      constructor() {
        super();
        
        this.state = { phone: "", error: "" };
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.handleReset = this.handleReset.bind(this);
      }
      
      handleChange({ target: { value } }) {   
        this.setState(prevState=> ({ phone: normalizeInput(value, prevState.phone) }));
      };
      
      handleSubmit(e) {
        e.preventDefault();
        const error = validateInput(this.state.phone);
        
        this.setState({ error }, () => {
           if(!error) {
             setTimeout(() => {
               alert(JSON.stringify(this.state, null, 4));
             }, 300)
           }
        });
      }
      
      handleReset() {
         this.setState({ phone: "", error: "" });
      };
      
      render() {
        return(
          <form className="form" onSubmit={this.handleSubmit}>
            <div className="input-container">
              <p className="label">Phone:</p>
              <input
                className="input"
                type="text"
                name="phone"
                placeholder="(xxx) xxx-xxxx"
                value={this.state.phone}
                onChange={this.handleChange}
              />
              {this.state.error && <p className="error">{this.state.error}</p>}
            </div>
            <div className="btn-container">
              <button 
                 className="btn danger"
                 type="button"
                 onClick={this.handleReset}
               >
                Reset
              </button>
              <button className="btn primary" type="submit">Submit</button>
            </div>
          </form>
        );
      }
    }
    
    ReactDOM.render(
      <Form />,
      document.getElementById('root')
    );
    html {
      font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
      font-size: 16px;
      font-weight: 400;
      line-height: 1.5;
      -webkit-text-size-adjust: 100%;
      background: #fff;
      color: #666;
    }
    
    .btn {
      color: #fff;
      border: 1px solid transparent;
      margin: 0 10px;
      cursor: pointer;
      text-align: center;
      box-sizing: border-box;
      padding: 0 30px;
      vertical-align: middle;
      font-size: .875rem;
      line-height: 38px;
      text-align: center;
      text-decoration: none;
      text-transform: uppercase;
      transition: .1s ease-in-out;
      transition-property: color,background-color,border-color;
    }
    
    .btn:focus {
      outline: 0;
    }
    
    .btn-container {
      text-align: center;
      margin-top: 10px;
    }
    
    .form {
      width: 550px;
      margin: 0 auto;
    }
    
    .danger {
      background-color: #f0506e;
      color: #fff;
      border: 1px solid transparent;
    }
     
    .danger:hover {
      background-color: #ee395b;
      color: #fff;
    }
    
    .error {
      margin: 0;
      margin-top: -20px;
      padding-left: 26%;
      color: red;
      text-align: left;
    }
    
    .input {
      display: inline-block;
      height: 40px;
      font-size: 16px;
      width: 70%;
      padding: 0 10px;
      background: #fff;
      color: #666;
      border: 1px solid #e5e5e5;
      transition: .2s ease-in-out;
      transition-property: color,background-color,border;
     }
    
    .input-container {
      width: 100%;
      height: 60px;
      margin-bottom: 20px;
      display: inline-block;
    }
    
    .label {
      width: 25%;
      padding-top: 8px;
      display: inline-block;
      text-align: center;
      text-transform: uppercase;
      font-weight: bold;
      height: 34px;
      border-top-left-radius: 4px;
      border-bottom-left-radius: 4px;
      background: rgb(238, 238, 238);
    }
    
    .primary {
      background-color: #1e87f0;
    }
    
    .primary:hover {
      background-color: #0f7ae5;
      color: #fff;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    
    
    <div id='root'>
    </div>

    或者...有 3 个单独的输入并在完成后将它们组合起来。

    const validateInput = value => {
      let error = ""
      
      if (!value) error = "Required!"
      else if (value.length !== 14) error = "Invalid phone format. ex: (555) 555-5555";
      
      return error;
    };
    
    const initialState = {
     areaCode: "",
     prefix: "",
     suffix: "",
     error: ""
    };
        
    class Form extends React.Component {
      constructor() {
        super();
        
        this.state = initialState;
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.handleReset = this.handleReset.bind(this);
        this.setInputRef = this.setInputRef.bind(this);
      }
      
      handleChange({ target: { name, value } }) {
        let valueChanged = false;
        this.setState(prevState => {
          const nextValue = value.replace(/[^\d]/g, '');
          const changedValue = prevState[name];
          if (changedValue.length !== nextValue.length) valueChanged = true;
      
          return { [name]: nextValue }
        }, () => {
          if(valueChanged) this.handleFocus(name)
        });
      };
      
      setInputRef(name, element) {
       this[name] = element;
      }
      
      handleFocus(name){
        const { areaCode, prefix, suffix } = this.state;
        const areaCodeFilled = areaCode.length === 3;
        const prefixFilled = prefix.length === 3;
    
        if(areaCodeFilled && name === "areaCode") {
          this.prefix.focus();
          this.prefix.selectionEnd = 0;
        } else if(prefixFilled && name === "prefix") {
          this.suffix.focus();
          this.suffix.selectionEnd = 0;
        }
      }
      
      handleSubmit(e) {
        e.preventDefault();
        const { areaCode, prefix, suffix } = this.state;
        const phoneNumber = `(${areaCode}) ${prefix}-${suffix}`
        const error = validateInput(phoneNumber);
        
        this.setState({ error }, () => {
           if(!error) {
             setTimeout(() => {
               alert(phoneNumber);
             }, 300)
           }
        });
      }
      
      handleReset() {
         this.setState(initialState);
      };
      
      render() {
        return(
          <form className="form" onSubmit={this.handleSubmit}>
            <div className="input-container">
              <div className="label">
                Phone:
              </div>
              <div className="parenthesis" style={{ marginLeft: 10, marginRight: 2}}>&#40;</div>
              <input
                className="input area-code"
                type="text"
                name="areaCode"
                placeholder="xxx"
                value={this.state.areaCode}
                onChange={this.handleChange}
                maxLength="3"
              />
               <div className="parenthesis" style={{ marginRight: 2}}>&#41;</div>
              <input
                ref={node => this.setInputRef("prefix", node)}
                className="input prefix"
                type="text"
                name="prefix"
                placeholder="xxx"
                value={this.state.prefix}
                onChange={this.handleChange}
                maxLength="3"
              />
              <div className="dash">-</div>
              <input
                ref={node => this.setInputRef("suffix", node)}
                className="input suffix"
                type="text"
                name="suffix"
                placeholder="xxxx"
                value={this.state.suffix}
                onChange={this.handleChange}
                maxLength="4"
              />
            </div>
             <p className="error">{this.state.error}</p>
            <div className="btn-container">
              <button 
                 className="btn danger"
                 type="button"
                 onClick={this.handleReset}
               >
                Reset
              </button>
              <button className="btn primary" type="submit">Submit</button>
            </div>
          </form>
        );
      }
    }
    
    ReactDOM.render(
      <Form />,
      document.getElementById('root')
    );
    html {
      font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
      font-size: 16px;
      font-weight: 400;
      line-height: 1.5;
      -webkit-text-size-adjust: 100%;
      background: #fff;
      color: #666;
    }
    
    .btn {
      color: #fff;
      border: 1px solid transparent;
      margin: 0 10px;
      cursor: pointer;
      text-align: center;
      box-sizing: border-box;
      padding: 0 30px;
      vertical-align: middle;
      font-size: .875rem;
      line-height: 38px;
      text-align: center;
      text-decoration: none;
      text-transform: uppercase;
      transition: .1s ease-in-out;
      transition-property: color,background-color,border-color;
    }
    
    .btn:focus {
      outline: 0;
    }
    
    .btn-container {
      text-align: center;
      margin-top: 10px;
    }
    
    .form {
      width: 550px;
      margin: 0 auto;
    }
    
    .danger {
      background-color: #f0506e;
      color: #fff;
      border: 1px solid transparent;
    }
     
    .danger:hover {
      background-color: #ee395b;
      color: #fff;
    }
    
    .error {
      margin: 0;
      height: 24px;
      margin-top: -20px;
      padding-left: 26%;
      color: red;
      text-align: right;
    }
    
    .input {
      display: flex;
      height: 40px;
      font-size: 16px;
      width: 33%;
      padding: 0 3px;
      background: #fff;
      color: #666;
      outline: none;
      border: 0;
    }
     
    .area-code,.prefix {
     width: 27px;
    }
    
    .suffix {
     width: 38px;
    }
    
    .dash,.parenthesis {
     display: flex;
    }
    
    .input-container {
      width: 100%;
      margin-bottom: 20px;
      display: flex;
      flex-direction: row;
      align-items: center;
      border-top-left-radius: 4px;
      border-bottom-left-radius: 4px;
    
        border: 1px solid #e5e5e5;
      transition: .2s ease-in-out;
      transition-property: color,background-color,borde
    }
    
    .label {
      height: 100%;
      background: rgb(238, 238, 238);
      width: 25%;
      padding-top: 8px;
      display: flex;
      text-transform: uppercase;
      justify-content: space-around;
      font-weight: bold;
      height: 34px;
    }
    
    .primary {
      background-color: #1e87f0;
    }
    
    .primary:hover {
      background-color: #0f7ae5;
      color: #fff;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    
    
    <div id='root'>
    </div>

    【讨论】:

    • 我在寻找stackoverflow.com/questions/61240217/… 的答案时找到了这个解决方案。我认为您的解决方案也存在我面临的问题,即“当您在字符串中间的某个位置进行编辑时,光标会跳到输入的末尾”。
    • 虽然我了解您要完成的工作,但不幸的是,使用此解决方案是不可能的(因为它会根据其长度对值进行切片)。相反,您可能需要考虑将输入分成 3 个输入:填写区号:“123”,然后一旦填写完毕,将光标聚焦在下一个输入:“567”上,一旦填写完毕,聚焦光标在最后四位:“8910”。每个都是一个独立的输入,但对用户来说,它似乎被格式化为一个输入。然后,更改一个不会影响另一个,因为它们是具有不同值的单独输入。
    • 更新了上面的示例以包含 3 个单独的输入。不完美,但它基本上做同样的事情。您还可以摆脱基于值变化对输入的自动关注,而是让用户选项卡通过它们。
    • Matt- 这是解决此问题的好方法。然而,我的问题是我在 JSON 响应中从后端获取输入字段的名称和 ID。当我提交表单时,后端希望我提交电话值作为分配给它首先发送的名称的一个输入字段。
    • 我没有发现问题。当您从后端检索它时,您可以将其保存到相应的组件状态...前 3 位:区号,后 3 位:前缀,后 4 位后缀。使用输入根据需要操作状态,然后在提交时,在将其发送到后端之前,将它们重新组合在一起(就像第二个示例在 onSubmit 函数中所做的那样),然后将其作为具有相同 id 的相同名称字段发送回。
    【解决方案2】:

    一切都与格式化有关。任何打印字符的键
    应该会导致重写输入字段。
    这样,无论他做什么,用户都只能看到有效的格式化字段。

    正则表达式很简单^\D*(\d{0,3})\D*(\d{0,3})\D*(\d{0,4})

    function getFormattedPhoneNum( input ) {
      let output = "(";
      input.replace( /^\D*(\d{0,3})\D*(\d{0,3})\D*(\d{0,4})/, function( match, g1, g2, g3 )
          {
            if ( g1.length ) {
              output += g1;
              if ( g1.length == 3 ) {
                  output += ")";
                  if ( g2.length ) {
                      output += " " + g2; 
                      if ( g2.length == 3 ) {
                          output += " - ";
                          if ( g3.length ) {
                              output += g3;
                          }
                      }
                  }
               }
            }
          }       
        );        
      return output;
     }       
    
    console.log( getFormattedPhoneNum("") );
    console.log( getFormattedPhoneNum("2") );
    console.log( getFormattedPhoneNum("asdf20as3d") );
    console.log( getFormattedPhoneNum("203") );
    console.log( getFormattedPhoneNum("203-44") );
    console.log( getFormattedPhoneNum("444sg52asdf22fd44gs") );
    console.log( getFormattedPhoneNum("444sg526sdf22fd44gs") );
    console.log( getFormattedPhoneNum("444sg526sdf2244gs") );
    console.log( getFormattedPhoneNum(" ra098 848 73653k-atui ") );

    你甚至可以变得更漂亮,并在字符的位置显示下划线
    应该在任何给定时间。
    点赞
    (___) ___ - ____
    (20_) ___ - ____
    (123) 456 - ____

    等等...(如果你想要这个,请告诉我)

    【讨论】:

    • 惊人的答案,正是我想要的。谢谢。
    • 如果某些国家/地区的运营商代码为 2 位数怎么办。我如何涵盖所有案例?
    【解决方案3】:

    您当前代码的正则表达式仅在输入十位数字(3、3、4)时匹配。您可以更新正则表达式以接受一系列数字,例如:

    ^\(?([0-9]{0,3})\)?[-. ]?([0-9]{0,3})[-. ]?([0-9]{0,4})$
    

    或者您可以让正则表达式简单地确保输入 0-10 位数字 ([0-9]{0,10}),然后自己将字符串拆分为长度为 3、3 和 4 的子字符串。这样做后一种方式似乎更好,因为您只想根据用户输入的数字显示某些字符:

    1 -> (1

    123 -> (123)

    1234567 -> (123) 456-7

    1234567890 -> (123) 456-7890

    您必须处理这些情况,而简单的替换无法做到。

    【讨论】:

      【解决方案4】:

      我用过这个库:https://www.npmjs.com/package/libphonenumber-js 它有一个 AsYouType 函数,你可以在你的输入上使用它

      【讨论】:

        【解决方案5】:

        也许,以下内容对某人有用:

           onChange={(e) => {
                        if (e.target.value.length < 13) {
                          var cleaned = ("" + e.target.value).replace(/\D/g, "");
        
                          let normValue = `${cleaned.substring(0, 3)}${
                            cleaned.length > 3 ? "-" : ""
                          }${cleaned.substring(3, 6)}${
                            cleaned.length > 6 ? "-" : ""
                          }${cleaned.substring(6, 11)}`;
        
                          handleChange(normValue);
                        }
                      }}
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-08-31
          相关资源
          最近更新 更多