React Material v5 (MUI 5) Masked Phone Number Validation
Table of Contents
Overview
Validating user inputs is an essential part of any web application. Ensuring that data is entered correctly helps to reduce errors and improve the overall user experience. This blog post will show you how to validate phone number format with React and Material UI. Material UI is a popular React UI library that provides a range of pre-built components and design elements. React, and Material UI makes it easy to create an input form with a phone number field and validate the format of the entered data. In this tutorial, we will guide you through the process of using React and Material UI to validate phone number format and provide tips and best practices to help you along the way.
The code includes MUI v4 and v5 at the Full Stack Soup GitHub Repo. The input uses the react-imask and react-number-format library and Material UI to build a phone input field that only accepts 10 digit phone numbers. The field will have the standard blue borders unless it has been touched. If the number does not meet the ten-digit validation rule, the field will have red borders using the error properties. The Class Component version of is not included but available from the GitHub repo.
Legacy Article for Material Version 4
Please check out the following article with source files on GitHub.
Source Files For This Article
Included are the source files for Material version 4 and 5.
Get Source CodeLibraries Required
React Material UI Version 5
Install the Material Core with Node Package Manager.
npm install @mui/material @emotion/react @emotion/styled
React Input Mask
Install the React Number Format and React IMask libraries with Node Package Manager.
npm install react-input-mask react-imask
Phone Input Field Component

The Input Component
The phone field uses the react imask library to render a masked pattern onto the field.
Build a masked phone number pattern with <IMaskInput/> from react imask.
const TextMaskCustom = React.forwardRef(function TextMaskCustom(props, ref) {
const { onChange, ...other } = props;
return (
<IMaskInput
{...other}
mask="(#00) 000-0000"
definitions={{
'#': /[1-9]/,
}}
inputRef={ref}
onAccept={(value) => onChange({ target: { name: props.name, value } })}
overwrite
/>
);
});
TextMaskCustom.propTypes = {
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
};
Passing the Mask Pattern to the Input
Using MUI 5’s <OutlinedInput/> property inputComponent, we can pass the pattern defined in TextMaskCustom created with <IMaskInput/>.
<OutlinedInput
...
inputComponent={TextMaskCustom}
/>
All together
<FormControl variant="standard">
<InputLabel htmlFor="text-mask-input"></InputLabel>
<OutlinedInput
helperText={props.helperText}
error={isDirtyField && !isValid}
onChange={handleChange}
onBlur={() => setIsDirtyField(true)}
name="phone"
label=""
size={'small'}
value={phoneNumber}
InputLabelProps={{
shrink: true,
}}
inputComponent={TextMaskCustom}
/>
<FormHelperText id="my-helper-text">{props.helperText}</FormHelperText>
</FormControl>
The entire functional component InputPhoneFIeld.js code below.
import React from 'react';
import PropTypes from 'prop-types';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import OutlinedInput from '@mui/material/OutlinedInput';
import { IMaskInput } from 'react-imask';
const TextMaskCustom = React.forwardRef(function TextMaskCustom(props, ref) {
const { onChange, ...other } = props;
return (
<IMaskInput
{...other}
mask="(#00) 000-0000"
definitions={{
'#': /[1-9]/,
}}
inputRef={ref}
onAccept={(value) => onChange({ target: { name: props.name, value } })}
overwrite
/>
);
});
TextMaskCustom.propTypes = {
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
};
export default function InputPhoneField(props) {
const [phoneNumber, setPhoneNumber] = React.useState('')
const [isDirtyField, setIsDirtyField] = React.useState(false)
const [isValid, setIsValid] = React.useState(false)
const handleChange = event => {
const val = event.target.value;
let count = 0;
for (let i = 0; i < val.length; i++) {
if (val.charAt(i) in [0,1,2,3,4,5,6,7,8,9]) count++
}
var isValid = (count === 10) ? true : false;
setPhoneNumber(val);
setIsValid(isValid);
props.handleChange(val, isValid);
}
return (
<>
<FormControl variant="standard">
<InputLabel htmlFor="text-mask-input"></InputLabel>
<OutlinedInput
helperText={props.helperText}
error={isDirtyField && !isValid}
onChange={handleChange}
onBlur={() => setIsDirtyField(true)}
name="phone"
label=""
size={'small'}
value={phoneNumber}
InputLabelProps={{
shrink: true,
}}
inputComponent={TextMaskCustom}
/>
<FormHelperText id="my-helper-text">{props.helperText}</FormHelperText>
</FormControl>
</>
);
}
Material Field Error Property

In addition to disabling a Submit Button, we can use the error property in Material <TextField /> which shows a red border. Create another variable to track if the field is pristine or dirty. This way, the field does not show an error before being touched. In the example below, we want to know if the field is dirty (has been modified by the user). Also, we can contain the states inside the input component
const [isValid, setIsValid] = useState(false)
const [dirty, setDirty] = useState(false);
Show Error
Using the error property to show a red border if the field does not have a valid phone number.

error={dirty && !isValid}
Pristine or Dirty Input
Using the onBlur() event, set the dirty to true. This means the user touched the field and went elsewhere on the form.
<OutlinedInput
error={dirty && isValid === false}
onBlur={() => setDirty(true)}
onChange={(e) => handleChange(e)}
name="phone"
label="Phone"
size={'small'}
value={phoneNumber}
InputLabelProps={{
shrink: true,
}}
inputComponent={TextMaskCustom}
/>
Parent Functional Component – No Formik
Source code for the parent component
import React, { useState } from 'react';
import Button from '@mui/material/Button';
import InputPhoneField from './InputPhoneField';
import { Box } from '@mui/system';
export default function ParentFormFuncComp()
{
const [phone, setPhone] = useState('')
const [isValidPhone, setIsValidPhone] = useState(false)
const handlePhoneNumberChange = (value, isValid) => {
setPhone(value)
setIsValidPhone(isValid)
}
const handleFormSubmit = () => {
alert(phone);
}
return (
<Box sx={{ marginTop: '40px' }}>
<InputPhoneField placeholder=""
helperText="(Required)"
label="Phone"
fieldName="Phone"
handleChange={handlePhoneNumberChange}
value={phone} />
{isValidPhone === true ?
<Button variant="contained" color="primary" onClick={handleFormSubmit}>
Submit
</Button>
:
<Button variant="contained" color="primary" disabled>
Submit
</Button>
}
</Box>
);
}
Validation With Formik
Formik with Yup are the most commonly used validation libraries for react. The method above is easier to understand at first but can result in more code to maintain.
Libraries Required
Formik and Yup are used for form validation.
npm install formik yup
Formik Input Phone Field Component
Source code for the entire FormikInputPhoneField.js component.
import React from 'react';
import PropTypes from 'prop-types';
import { alpha, styled } from '@mui/material/styles';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import OutlinedInput from '@mui/material/OutlinedInput';
import { IMaskInput } from 'react-imask';
const TextMaskCustom = React.forwardRef(function TextMaskCustom(props, ref) {
const { onChange, ...other } = props;
return (
<IMaskInput
{...other}
mask="(#00) 000-0000"
definitions={{
'#': /[1-9]/,
}}
inputRef={ref}
onAccept={(value) => onChange({ target: { name: props.name, value } })}
overwrite
/>
);
});
TextMaskCustom.propTypes = {
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
};
const ValidationOutlinedInputField = styled(OutlinedInput)({
'& input:valid + fieldset': {
borderColor: 'green',
borderWidth: 2,
},
'& input:invalid + fieldset': {
borderColor: 'red',
borderWidth: 2,
},
'& input:valid:focus + fieldset': {
borderLeftWidth: 6,
padding: '4px !important', // override inline-style
},
'&:hover fieldset': {
borderColor: 'yellow',
},
});
export default function InputPhoneField(props) {
const [phoneNumber, setPhoneNumber] = React.useState('')
const [isDirtyField, setIsDirtyField] = React.useState(false)
const [isValid, setIsValid] = React.useState(false)
const handleChange = event => {
const val = event.target.value;
let count = 0;
for (let i = 0; i < val.length; i++)
if (val.charAt(i) in [0,1,2,3,4,5,6,7,8,9])
count++
var isValid = (count === 10) ? true : false;
setPhoneNumber(val);
setIsValid(isValid);
props.handleChange(val, isValid);
}
return (
<>
<FormControl variant="standard">
<InputLabel htmlFor="text-mask-input"></InputLabel>
<ValidationOutlinedInputField
value={props.formik.values.phone}
error={props.formik.touched.phone && Boolean(props.formik.errors.phone)}
onChange={props.formik.handleChange}
name="phone"
label=""
size={'small'}
style={{marginTop: -1}}
InputLabelProps={{
shrink: true,
}}
inputComponent={TextMaskCustom}
/>
<FormHelperText id="my-helper-text">{props.formik.touched.phone && props.formik.errors.phone}</FormHelperText>
</FormControl>
</>
);
}
Break Down of the FormikInputPhoneField.js Component
The name, value, error, helper properties or in this case <FormHelperText> must be set as below. The onChange event used formik’s predefined handleChange method. The error property changes the border color to red if both the touched and error conditions are true.
name="phone"
value={props.formik.values.phone}
error={props.formik.touched.phone && Boolean(props.formik.errors.phone)}
onChange={props.formik.handleChange}
Display the Error & Warning Messages
For the helper text that displays errors

And warnings

Using MUI’s FormHelperText component, set the help and error messages.
<FormHelperText id="my-helper-text">{props.formik.touched.phone && props.formik.errors.phone}</FormHelperText>
For MUI’s <TextField> use the helper property to display the error and warning messages.
helper={props.formik.touched.phone && props.formik.errors.phone}
The Parent Component
Complete Source Code for the Parent Component FormikParentFormFuncComp.js
import React, {useState, useEffect} from 'react';
import Button from '@mui/material/Button';
import { Box } from '@mui/material';
import { useFormik } from 'formik';
import * as yup from 'yup';
import InputPhoneField from './FormikInputPhoneField';
export default function FormikParentFormFuncComp() {
const [phone, setPhone] = useState('')
const [isValidPhone, setIsValidPhone] = useState(false)
const handlePhoneNumberChange = (value, isValid) => {
console.log(value);
console.log(isValid);
setPhone(value)
setIsValidPhone(isValid)
}
const handleFormSubmit = () => {
var data = {
Phone: formik.values.phone,
}
alert(JSON.stringify(data, null, 2));
}
const phoneRegExp = /^((\+[1-9]{1,4}[ -]?)|(\([0-9]{2,3}\)[ -]?)|([0-9]{2,4})[ -]?)*?[0-9]{3,4}[ -]?[0-9]{3,4}$/;
const validationSchema = yup.object({
phone: yup
.string('Enter your phone number')
.matches(phoneRegExp, 'Phone number must have 10 digiits')
.required('Phone is required'),
});
const formik = useFormik({
initialValues: {
phone: '',
},
validationSchema: validationSchema ,
onSubmit: values => {
// Handle Submit
handleFormSubmit()
},
});
return (
<>
<Box sx={{ marginTop: '40px' }}>
<InputPhoneField placeholder=""
formik={formik}
helperText="(Required)"
label="Phone"
fieldName="Phone"
handleChange={handlePhoneNumberChange}
value={phone} />
<Button variant="contained" color="primary" onClick={formik.handleSubmit}>
Submit
</Button>
</Box>
</>
);
}
Break Down of the FormikParentFormFuncComp.js Component
Imports
Import the following libraries including the FormikInputPhoneField.js from above.
import React, {useState, useEffect} from 'react';
import Button from '@mui/material/Button';
import { Box } from '@mui/material';
import { useFormik } from 'formik';
import * as yup from 'yup';
import InputPhoneField from './FormikInputPhoneField';
Yup Validation Schema
Create a validation schema with Yup, This can validate against the masked phone number Regex.
const phoneRegExp = /^((\+[1-9]{1,4}[ -]?)|(\([0-9]{2,3}\)[ -]?)|([0-9]{2,4})[ -]?)*?[0-9]{3,4}[ -]?[0-9]{3,4}$/;
const validationSchema = yup.object({
phone: yup
.string('Enter your phone number')
.matches(phoneRegExp, 'Phone number must have 10 digiits')
.required('Phone is required'),
});
Formik
Create a formik function with the useFormik() hook. The validationSchema defined with Yup will check if the input is valid and if it passes then submit the form.
const formik = useFormik({
initialValues: {
phone: '',
},
validationSchema: validationSchema ,
onSubmit: values => {
// Handle Submit
},
});
Render the Phone Input Component
Add the property formik={formik}
<Box sx={{ marginTop: '40px' }}>
<InputPhoneField placeholder=""
formik={formik}
helperText="(Required)"
label="Phone"
fieldName="Phone"
handleChange={handlePhoneNumberChange}
value={phone} />
<Button variant="contained" color="primary" onClick={formik.handleSubmit}>
Submit
</Button>
</Box>
Conclusion
In conclusion, validating phone number format is crucial in building a web application that provides a positive user experience. Using React and Material UI, you can create an input form with a phone number field and validate the format of the entered data efficiently and effectively. Throughout this blog post, we walked through the process of using React and Material UI to validate phone number format. Whether you are a seasoned developer or just starting, this tutorial provides a great introduction to using React and Material UI for input validation. It will help you create more robust and user-friendly web applications for your next project.
You must be logged in to post a comment.