Lesson 12: Forms - strvcom/frontend-academy-2022 GitHub Wiki
Speaker: Lukas Polak
- Pull Request
- Recording (Google Drive)
- Slides (Google Slides)
- Slides (PDF)
- We need to use form controls like
input
,textarea
,select
,button
-
form
tag needs to havemethod
andaction
attributes - State (values) is managed by the DOM
<form action="/some-url" method="GET">
<label for="email">Email</label>
<input type="email" id="email" name="email" />
<label for="password">Password</label>
<input type="password" id="password" name="password" />
<!-- We do not need to specify `type=submit` as it is an default value -->
<!-- We can also use `<input type="submit">` -->
<button>Submit</button>
</form>
<body>
<form action="/some-url" method="GET">
<label for="email">Email</label>
<input type="email" id="email" name="email" />
<br />
<label for="password">Pass</label>
<input type="password" id="password" name="password" />
</form>
<hr />
<form action="/some-url" method="GET">
<label for="name">Name</label>
<input type="text" id="name" name="name" />
<br />
<label for="number">Age</label>
<input type="number" id="number" name="number" />
<br />
<label for="tel">Tel</label>
<input type="tel" id="tel" name="tel" />
</form>
</body>
-
form
tag needs to haveonSubmit
callback instead ofmethod
andaction
attributes - For easier "data manipulation", we can use the
FormData
constructor to create key/value pairs representing form fields and their values.
export default function App() {
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
// prevent page reload on form submission
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const email = formData.get("email");
const password = formData.get("password");
alert(`email: ${email}\npassword: ${password}`);
};
return (
// instead of `action` and `method` attributes we will use only `onSubmit` prop
<form onSubmit={handleSubmit}>
{/* change `for` to `htmlFor` */}
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" />
{/* change `for` to `htmlFor` */}
<label htmlFor="email">Password</label>
<input type="password" id="password" name="password" />
<button>Submit</button>
</form>
);
}
- Local state is managed by DOM
- Less configuration & less flexible
-
<input type="file" />
is always uncontrolled as value cannot be set programmatically - To specify initial value we can use
defaultValue
prop - The term “uncontrolled” is not tightly coupled with the form elements!
- The state is driven by props; React manages the state
- more configuration & more flexibility
-
value
andonChange
props are required - React team recommends using controlled components
export default function App() {
// initialize states for the input values
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
alert(`email: ${email}\npassword: ${password}`);
};
// initialize `onChange` callbacks
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
// pass `value` and `onChange` props
value={email}
onChange={handleEmailChange}
/>
<label htmlFor="email">Password</label>
<input
type="password"
id="password"
name="password"
// pass `value` and `onChange` props
value={password}
onChange={handlePasswordChange}
/>
<button>Submit</button>
</form>
);
}
export default function App() {
// initialize states for the input values
const [values, setValues] = useState<{ email?: string; password?: string }>(
{}
);
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
alert(`email: ${values?.email}\npassword: ${values?.password}`);
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setValues({ ...values, [e.target.name]: e.target.value });
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
value={values.email}
onChange={handleChange}
/>
<label htmlFor="email">Password</label>
<input
type="password"
id="password"
name="password"
value={values.password}
onChange={handleChange}
/>
<button>Submit</button>
</form>
);
}
- React uses
SyntheticEvent
, which is a wrapper around the browser's native event - It works identically across all browsers
- It has the same interface as the browser's native event
- It has a better performance
- To get a native browser event, you can use the
nativeEvent
attribute
- Every major browser has native components that show validation messages
- The same validation "error" looks different in different browsers.
- Uses Constraint Validation API in the background
- Easy to use, but hard to tweak
import { FormEvent } from "react";
export default function App() {
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("data submitted");
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
{/* add `required` prop */}
<input type="email" id="email" name="email" required />
<label htmlFor="email">Password</label>
{/* add `required` prop */}
<input type="password" id="password" name="password" required />
<label htmlFor="string">String</label>
<input
type="text"
id="string"
name="string"
// add `required` prop
required
// add `pattern` prop
pattern="[A-Za-z]+"
/>
<button>Submit</button>
</form>
);
}
- It enables us to check the values that users have entered before submitting the values to the server.
- Widely supported
- Harder to configure, but more flexible than with built-in validations UI
- It doesn't have "bulletproof" validations
- It doesn't replace the backend validations!
import { FocusEvent, useState } from "react";
export default function App() {
const [error, setError] = useState("");
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
if (e.target.validity.valid) {
setError("");
} else {
setError(e.target.validationMessage);
}
};
return (
<form>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
required
onBlur={handleBlur}
// turn off native validation
formNoValidate
/>
{error ? <span>{error}</span> : null}
</form>
);
}
- Quick and easy way to abstract complex logic in forms
- Reduce repetitive tasks
- Too much abstraction could obscure things and make customization difficult
- Increased bundle size
- You have only one form in the app
- You have a simple form logic
- You want to challenge yourself
- You have lots of forms
- You have a complex validation logic
- You do not have time to tinker own solution
- Show errors as soon as possible but in a reasonable way
- Use proper semantics
- Show loading state while submission
- Disable button while loading state is present
- Use proper attributes (
tel
email
autocomplete
) to improve the experience (not only) for mobile users - Do not disable the button when validation is depended on the submission
-
<input type="date" />
and<input type="time" />
are supported in Safari since version 14.1 (September 13, 2021)- Chrome has supported that since version 20 (June 26, 2012)
- IE has no support, and IE is not dead yet and won't be dead for a few years now.
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
- https://developer.mozilla.org/en-US/docs/Learn/Forms
- https://developer.mozilla.org/en-US/docs/Web/API/FormData
- https://reactjs.org/docs/forms.html#controlled-components
- https://beta.reactjs.org/learn/sharing-state-between-components#step-3-add-state-to-the-common-parent
- https://reactjs.org/docs/events.html
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#client-side_validation
- https://developer.mozilla.org/en-US/docs/Web/API/Constraint_validation
- https://github.com/strvcom/frontend-academy-2022/pull/11
- https://github.com/strvcom/frontend-academy-2022/pull/12