Храните только десять национальных цифр, форматируйте их для показа и восстанавливайте курсор по числу цифр перед ним. Удаление разделителя должно удалять соседнюю цифру.
Автоматическое приведение ввода к телефонному формату
Храните только десять национальных цифр, форматируйте их для показа и восстанавливайте курсор по числу цифр перед ним. Удаление разделителя должно удалять соседнюю цифру.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Создайте React‑компонент PhoneNumberInput, который будет динамически преобразовывать вводимые символы в поле ввода в привычный вид телефонного номера, например +7 (___) ___--. При наборе пользователь видит уже отформатированный номер, без необходимости вручную вставлять скобки, пробелы или дефисы.
import "./styles.css";
import { PhoneNumberInput } from "./Input";
export default function App() {
return (
<div className="App">
<div>Input</div>
<PhoneNumberInput />
<br />
<br />
<div>Your Input</div>
</div>
);
}
Задача подразумевает обработку событий ввода, работу с курсором и поддержание корректного формата в реальном времени.
Ответ
Храните только десять национальных цифр, форматируйте их для показа и восстанавливайте курсор по числу цифр перед ним. Удаление разделителя должно удалять соседнюю цифру.
Для маски +7 (XXX) XXX-XX-XX минимальная реализация ниже поддерживает вставку, выделение и Backspace/Delete:
function PhoneNumberInput() {
const [digits, setDigits] = React.useState('');
const input = React.useRef(null);
const caret = React.useRef(null);
const format = value => '+7 (' + value.slice(0,3) + (value.length >= 3 ? ') ' : '') + value.slice(3,6) + (value.length > 6 ? '-' + value.slice(6,8) : '') + (value.length > 8 ? '-' + value.slice(8,10) : '');
const [revision, refresh] = React.useState(0);
function commit(value, index) {
setDigits(value); caret.current = index; refresh(n => n + 1);
}
React.useLayoutEffect(() => {
if (caret.current === null) return;
const text = format(digits);
let pos = 4, seen = 0;
while (pos < text.length && seen < caret.current) {
if (/[0-9]/.test(text[pos])) seen++;
pos++;
}
input.current.setSelectionRange(pos, pos);
caret.current = null;
}, [digits, revision]);
function ordinal(text, position) { return Math.max(0, (text.slice(0, position).match(/[0-9]/g) || []).length - 1); }
function change(event) {
const node = event.target;
const raw = node.value.replace(/[^0-9]/g, '');
const prefix = /^[78]/.test(raw) && (node.value.startsWith('+') || raw.length > 10) ? 1 : 0;
const count = (node.value.slice(0, node.selectionStart).match(/[0-9]/g) || []).length;
commit(raw.slice(prefix, prefix + 10), Math.max(0, count - prefix));
}
function keyDown(event) {
if (event.key !== 'Backspace' && event.key !== 'Delete') return;
event.preventDefault();
const node = event.currentTarget;
let from = ordinal(node.value, node.selectionStart);
let to = ordinal(node.value, node.selectionEnd);
if (from === to) {
if (event.key === 'Backspace') from = Math.max(0, from - 1);
else to = Math.min(digits.length, to + 1);
}
commit(digits.slice(0, from) + digits.slice(to), from);
}
return <label>Телефон <input ref={input} type='tel' inputMode='tel' value={format(digits)} onChange={change} onKeyDown={keyDown} /></label>;
}
Это учебная маска фиксированного кода страны, не международная валидация телефона. Для production отдельно проверяют мобильное редактирование, autofill, IME и доступность; сервер проверяет итоговые данные независимо.