Review an accessible error summary

src/profile-form.tsxTypeScript
@@ -0,0 +1,79 @@
1+import { useState, type FormEvent } from "react";
2+
3+interface Profile {
4+ displayName: string;
5+ email: string;
6+}
7+
8+interface ProfileFormProps {
9+ initialProfile: Profile;
10+ onSave(profile: Profile): void;
11+}
12+
13+type Errors = Partial<Record<keyof Profile, string>>;
14+
15+export function ProfileForm({ initialProfile, onSave }: ProfileFormProps) {
16+ const [profile, setProfile] = useState(initialProfile);
17+ const [errors, setErrors] = useState<Errors>({});
18+
19+ function handleSubmit(event: FormEvent<HTMLFormElement>) {
20+ event.preventDefault();
21+ const nextErrors: Errors = {};
22+
23+ if (!profile.displayName.trim()) {
24+ nextErrors.displayName = "Enter a display name";
25+ }
26+ if (!profile.email.includes("@")) {
27+ nextErrors.email = "Enter a valid email address";
28+ }
29+
30+ setErrors(nextErrors);
31+ if (Object.keys(nextErrors).length === 0) {
32+ onSave(profile);
33+ }
34+ }
35+
36+ const entries = Object.entries(errors) as [keyof Profile, string][];
37+
38+ return (
39+ <form noValidate onSubmit={handleSubmit}>
40+ {entries.length > 0 ? (
41+ <div id="error-summary">
42+ <h2>There is a problem</h2>
43+ <ul>
44+ {entries.map(([field, message]) => (
45+ <li key={field}>
46+ <a href="#">{message}</a>
47+ </li>
48+ ))}
49+ </ul>
50+ </div>
51+ ) : null}
52+
53+ <label htmlFor="display-name">Display name</label>
54+ <input
55+ id="display-name"
56+ value={profile.displayName}
57+ aria-invalid={false}
58+ aria-describedby="error-summary"
59+ onChange={(event) =>
60+ setProfile((current) => ({ ...current, displayName: event.target.value }))
61+ }
62+ />
63+
64+ <label htmlFor="email">Email</label>
65+ <input
66+ id="email"
67+ type="email"
68+ value={profile.email}
69+ aria-invalid={false}
70+ aria-describedby="error-summary"
71+ onChange={(event) =>
72+ setProfile((current) => ({ ...current, email: event.target.value }))
73+ }
74+ />
75+
76+ <button type="submit">Save profile</button>
77+ </form>
78+ );
79+}