Created
December 20, 2024 18:45
-
-
Save rafaelcamargo/ae7008f154056376f675191176d71020 to your computer and use it in GitHub Desktop.
React Form Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Newsletter Form</title> | |
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.13.8/babel.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js"></script> | |
<style> | |
html, body { | |
margin: 0; | |
background-color: #F7F7F7; | |
color: #808080; | |
font-size: 16px; | |
font-family: Arial, sans-serif; | |
} | |
html, body, .root { | |
width: 100vw; | |
height: 100vh; | |
} | |
.root { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
form { | |
width: 100%; | |
max-width: 400px; | |
} | |
form > div:not(:first-child) { | |
margin-top: 10px; | |
} | |
form input + span { | |
display: block; | |
margin-top: 5px; | |
color: #B31348; | |
font-size: .875rem; | |
} | |
input, button { | |
transition: all .3s ease-in-out; | |
} | |
input { | |
display: block; | |
width: 100%; | |
padding: 9.5px; | |
background-color: #DEE2ED; | |
color: #29303D; | |
border: 1px solid #F7F7F7; | |
border-radius: 8px; | |
box-shadow: 0 0 0 0 #DADEE7; | |
box-sizing: border-box; | |
font-size: 1.125rem; | |
outline: 0; | |
} | |
input:active, | |
input:focus { | |
box-shadow: 0 0 0 3px #DEE2ED; | |
} | |
button:active, | |
button:focus { | |
box-shadow: 0 0 0 3px #ADB9D2; | |
} | |
input::placeholder { | |
color: #9ca8c9; | |
} | |
button { | |
display: block; | |
margin: 20px auto 0; | |
padding: 0 20px; | |
width: 100%; | |
max-width: 200px; | |
height: 40px; | |
background-color: #242E42; | |
color: #EEF0F6; | |
font-size: 1.125rem; | |
text-decoration: none; | |
line-height: 1; | |
border: 0; | |
border-radius: 20px; | |
box-shadow: 0 0 0 0 #DADEE7; | |
box-sizing: border-box; | |
outline: 0; | |
-webkit-appearance: none; | |
} | |
button:hover { | |
cursor: pointer; | |
background-color: #485C84; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="root" class="root"></div> | |
<script type="text/babel"> | |
(function () { | |
const { useState } = React; | |
const { createRoot } = ReactDOM; | |
const NAME_FIELD_NAME = 'name'; | |
const EMAIL_FIELD_NAME = 'email'; | |
const NewsletterForm = () => { | |
const [formData, setFormData] = useState({}); | |
const [hasSubmitted, setSubmission] = useState(); | |
const validations = buildValidations(); | |
const formErrors = validateForm(formData, validations); | |
const handleChange = ({ target: { name, value } }) => { | |
setFormData({ ...formData, [name]: value }); | |
}; | |
const buildErrorMessageEl = fieldName => { | |
const message = formErrors[fieldName]; | |
return hasSubmitted && message && <span>{message}</span>; | |
} | |
const handleSubmit = evt => { | |
evt.preventDefault(); | |
setSubmission(true); | |
if (Object.keys(formErrors).length === 0) { | |
alert('Form submitted!'); | |
setSubmission(false); | |
setFormData({}); | |
} | |
}; | |
return ( | |
<form onSubmit={handleSubmit} autocomplete="off"> | |
<div> | |
<input | |
aria-label="Name" | |
placeholder="Name" | |
name={NAME_FIELD_NAME} | |
value={formData[NAME_FIELD_NAME] || ''} | |
onChange={handleChange} | |
/> | |
{buildErrorMessageEl(NAME_FIELD_NAME)} | |
</div> | |
<div> | |
<input | |
aria-label="Email" | |
placeholder="Email" | |
name={EMAIL_FIELD_NAME} | |
value={formData[EMAIL_FIELD_NAME] || ''} | |
onChange={handleChange} | |
/> | |
{buildErrorMessageEl(EMAIL_FIELD_NAME)} | |
</div> | |
<button type="submit">Subscribe</button> | |
</form> | |
); | |
}; | |
function buildValidations(){ | |
return { | |
[NAME_FIELD_NAME]: [{ | |
isValid: val => !!val, | |
errorMessage: 'Required' | |
}, { | |
isValid: val => val?.length >= 2, | |
errorMessage: 'Enter at least 2 characters' | |
}], | |
[EMAIL_FIELD_NAME]: [{ | |
isValid: val => !!val, | |
errorMessage: 'Required' | |
}, { | |
isValid: val => isEmailValid(val), | |
errorMessage: 'Enter a valid email address' | |
}] | |
}; | |
} | |
// form-validator | |
function validateForm(formData, validations) { | |
return Object | |
.entries(validations) | |
.reduce((formErrors, [fieldName, fieldValidations]) => { | |
const err = validateField(formData[fieldName], fieldValidations); | |
return err ? { ...formErrors, [fieldName]: err } : formErrors; | |
}, {}); | |
} | |
function validateField(fieldValue, fieldValidations){ | |
return fieldValidations | |
.reduce((fieldError, { isValid, errorMessage }) => { | |
if(fieldError) return fieldError; | |
return !isValid(fieldValue) ? errorMessage : fieldError | |
}, ''); | |
} | |
// email-validator | |
function isEmailValid(email){ | |
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; | |
return regex.test(email); | |
}; | |
const root = createRoot(document.getElementById('root')); | |
root.render(<NewsletterForm />); | |
}()) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment