Skip to content

Instantly share code, notes, and snippets.

@mlh758
Last active June 11, 2022 19:33
Show Gist options
  • Save mlh758/b133882b70f5a7c5ccdae3a61be82a19 to your computer and use it in GitHub Desktop.
Save mlh758/b133882b70f5a7c5ccdae3a61be82a19 to your computer and use it in GitHub Desktop.
Uncontrolled Forms Blog Post
import React, { useRef, useEffect } from "react";
interface Option {
name: string;
label: string;
}
interface CheckboxesProps {
options: Option[];
}
export const Checkboxes: React.VFC<CheckboxesProps> = ({ options }) => {
// Store a ref to a container element to make it easy to select the checkboxes
const parentEl = useRef<HTMLFieldSetElement>(null);
// Use that ref to grab all the checkboxes.
const checkboxes = () => (parentEl.current?.querySelectorAll('input[type="checkbox"]') ?? []) as NodeListOf<HTMLInputElement>;
// setCustomValidity with a non-empty string marks the element as invalid. The message will be shown if you try to submit the form
const setAllInvalid = (nodes: NodeListOf<HTMLInputElement>) => {
nodes.forEach(checkbox => checkbox.setCustomValidity("You must select at least one option"));
}
// setCustomValidity with an empty string marks the element as valid
const setAllValid = (nodes: NodeListOf<HTMLInputElement>) => {
nodes.forEach(checkbox => checkbox.setCustomValidity(""));
}
// When any checkbox changes, see if any are checked and set the validity
const handleChange = () => {
let anyChecked = false;
const boxes = checkboxes();
boxes.forEach(checkbox => anyChecked = anyChecked || checkbox.checked);
anyChecked ? setAllValid(boxes) : setAllInvalid(boxes);
}
useEffect(() => {
setAllInvalid(checkboxes())
}, [])
return (
<fieldset ref={parentEl}>
<legend>Select at least one</legend>
{options.map((opt) => (
<label key={opt.name}>
<span>{opt.label}</span>
<input type="checkbox" name={opt.name} onChange={handleChange}/>
</label>
))}
</fieldset>
);
};
import React, { useCallback } from "react";
import { makeStyles, FormHelperText } from "@material-ui/core";
import { Checkboxes } from "./Checkboxes";
// Just some basic styling to keep the form flowing top to bottom
// with a little spacing.
const useStyles = makeStyles((theme) => ({
validatedForm: {
"& label": {
display: "block",
marginBottom: theme.spacing(2),
"& > span": {
marginRight: theme.spacing(1),
},
},
"& *:invalid": {
boxShadow: `0 0 5px 1px ${theme.palette.error.main}`,
},
"& *:focus:invalid": {
boxShadow: "none",
},
"& *:valid ~ p": {
display: "none",
},
},
}));
const checkboxOptions = [
{
label: "I consent to receiving marketing communications",
name: "consents",
},
{
label: "I do not consent to opting out of marketing communications",
name: "stillConsents",
},
];
export const Form = () => {
const handleSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
formData.forEach((val, key) => console.log(`${key} => ${val}`));
}, []);
const styles = useStyles();
return (
<div>
<h1>Give me all your personal information</h1>
<form onSubmit={handleSubmit} className={styles.validatedForm}>
<label>
<span>Legal Name</span>
<input name="fullname" />
<FormHelperText>Enter your full name</FormHelperText>
</label>
<label>
<span>Date of Birth</span>
<input type="date" name="dob" required />
<FormHelperText>Enter your date of birth</FormHelperText>
</label>
<label>
<span>Tell us about yourself</span>
<textarea minLength={20} maxLength={120} required />
<FormHelperText>Use 20-120 characters to tell us about yourself</FormHelperText>
</label>
<Checkboxes options={checkboxOptions} />
<button type="submit">Submit</button>
</form>
</div>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment