form
SeungAh Hong
Javascript Form
Design
hook-form vanilla javascript design

Javascript Form vs React Form
easier data control
// vanila javascript 사용
//// FormModal.ts
export default class FormModel {
data: formTypes;
constructor(key: string, options: formOptions) {
this.data = {
key,
value: '',
required: options.required,
minLength: options.minLength,
};
}
getValue(): string {
return this.data.value;
}
setValue(_value: string): void {
this.data.value = _value;
}
getValid(): { key: string; message: string } | void {
if (this.data.required && this.data.value === '') {
return { key: this.data.key, message: this.data.required };
}
if (
this.data.minLength &&
this.data.minLength.value > this.data.value.length
) {
return { key: this.data.key, message: this.data.minLength.message };
}
}
}
//// FormController.ts
userNameModel.setValue((event as CustomEvent).detail);
emailModel.setValue((event as CustomEvent).detail);
passwordModel.setValue((event as CustomEvent).detail);
// react 사용(custom hooks)
const useHookFormRegister = (options: hookOptions) => {
const [value, setValue] = useState('');
const ref = useRef<HTMLInputElement>(null);
const getValid = ({ focus }: validateOptions) => {
const { required, minLength } = options;
if (focus) {
ref.current?.focus();
}
if (!value && required) {
return required;
}
if (!value && minLength && value.length < minLength.value) {
return minLength.message;
}
return '';
};
return [value, setValue, ref, getValid] as const;
};
setUserName(event.target.value);
setEmail(event.target.value);
setPassword(event.target.value);easier inputs, inputs event
// vanila javascript 사용
//// FormView.ts
////// useState
this.emailInput = {
key: 'email',
element: document.getElementById('email') as HTMLInputElement,
};
////// onChange, onBlur
on(this.userNameInput.element, 'change', (event) => {
this.emit('userName@change', (event.target as HTMLInputElement).value);
});
on(this.passwordInput.element, 'blur', () => {
this.emit('@blur');
});
this.on('submit', (event) => {
event.preventDefault();
this.emit('handleSubmit');
});
//// FormController.ts
.on('userName@change', (event) => {
const { userNameModel } = this.formModel;
userNameModel.setValue((event as CustomEvent).detail);
})
.on('handleSubmit', (event) => {
}
// react 사용
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEmail(event.target.value);
};
const handleSubmit = (event: React.ChangeEvent<HTMLFormElement>) => {
event.preventDefault();
}
<form onSubmit={handleSubmit}>
<input
value={userName}
onChange={handleUsernameChange}
onBlur={handleBlur}
type="text"
placeholder="이름을 입력하세요."
ref={userNameRef}
/>
<input
value={email}
onChange={handleEmailChange}
onBlur={handleBlur}
type="email"
ref={emailRef}
placeholder="이메일을 입력하세요."
/>
<input
value={password}
onChange={handlePasswordChange}
onBlur={handleBlur}
type="password"
ref={passwordRef}
placeholder="패스워드를 입력하세요."
/>
<input type="submit" />
</form>better validation
// vanila javascript 사용
//// FormModel.ts
getValid(): { key: string; message: string } | void {
if (this.data.required && this.data.value === '') {
return { key: this.data.key, message: this.data.required };
}
if (
this.data.minLength &&
this.data.minLength.value > this.data.value.length
) {
return { key: this.data.key, message: this.data.minLength.message };
}
}
//// FormController.ts
const userName = userNameModel.getValid();
if (userName) {
this.formView.validate({
message: userName.message,
focus: userName.key,
});
}
//// FormView.ts
validate(validateOptions: validateViewOptions) {
const { message, focus: focusKey } = validateOptions;
set(this.errorDiv.element as HTMLDivElement, message);
if (this.userNameInput.key === focusKey) {
focus(this.userNameInput.element);
} else if (this.emailInput.key === focusKey) {
focus(this.emailInput.element);
} else if (this.passwordInput.key === focusKey) {
focus(this.passwordInput.element);
}
}
// react 사용
const useHookFormRegister = (options: hookOptions) => {
const [value, setValue] = useState("");
const ref = useRef<HTMLInputElement>(null);
const getValid = ({ focus }: validateOptions) => {
const { required, minLength } = options;
if (focus) {
ref.current?.focus();
}
if (!value && required) {
return required;
}
if (!value && minLength && value.length < minLength.value) {
return minLength.message;
}
return "";
};
return [value, setValue, ref, getValid] as const;
};
let errorMessage = "";
if ((errorMessage = userNameGetValid({ focus: true })) !== "") {
setError(errorMessage);
} else if ((errorMessage = emailGetValid({ focus: true })) !== "") {
setError(errorMessage);
} else if ((errorMessage = passwordGetValid({ focus: true })) !== "") {
setError(errorMessage);
} else {
// 성공
setUserName("");
setEmail("");
setPassword("");
setError("");
}React Form vs React-Hook-Form
Introduction to react-hook-form and its advantages
A library that makes building forms in React easy. As stated in the official docs, it is performant, flexible, and excellent at validation.
Advantages
- less code, easier inputs
- don’t deal with events
- better validation
- Controlled at the JavaScript layer rather than through HTML attributes
- Can defend against malicious attacks (if HTML attributes are modified, the browser fails to recognize them: required, minLength, etc.)
- Controlled at the JavaScript layer rather than through HTML attributes
- better error
- have control over inputs
- Supports TS out of the box
- Uses functional components (Hook API)
Installation
npm i react-hook-form // yarn add react-hook-formSample code
https://github.com/seungahhong/form-tutorial
Concepts
register
- This function lets you register an input/select
Refand apply validation through React Hook Form.
const { register } = useForm();
console.log(register('userName'));
controller
- When you cannot access a custom component and its
Ref, you can register the input manually. This can actually be used when working with custom components such as React Native or react-select. It is also used to handle this work on your behalf.
const { control } = useForm();
console.log(control);
<Controller
name="name"
control={control}
rules={
required: 'name is Required',
minLength: {
message: 'name 길이가 5보다 커야합니다.',
value: 5,
},
}
render={({ field }) => {
console.log(field);
return <Input {...field} />;
}}
/>;console.log(control);

console.log(field);

controller vs custom register
controller
import React, { useEffect } from 'react';
import { Input, Select, MenuItem } from '@material-ui/core';
import { useForm, Controller } from 'react-hook-form';
const defaultValues = {
select: '',
input: '',
};
function App() {
const { handleSubmit, reset, watch, control } = useForm({ defaultValues });
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
render={({ field }) => (
<Select {...field}>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
</Select>
)}
control={control}
name="select"
defaultValue={10}
/>
<Input {...register('input')} />
<button type="button" onClick={() => reset({ defaultValues })}>
Reset
</button>
<input type="submit" />
</form>
);
}custom register
import React, { useEffect } from 'react';
import { Input, Select, MenuItem } from '@material-ui/core';
import { useForm } from 'react-hook-form';
const defaultValues = {
select: 10,
input: '',
};
function App() {
const { register, handleSubmit, setValue, reset, watch } = useForm({
defaultValues,
});
const selectValue = watch('select');
const onSubmit = data => console.log(data);
useEffect(() => {
register({ name: 'select' });
}, [register]);
const handleChange = e => setValue('select', e.target.value);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Select value={selectValue} onChange={handleChange}>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
</Select>
<Input {...register('input')} />
<button type="button" onClick={() => reset({ ...defaultValues })}>
Reset
</button>
<input type="submit" />
</form>
);
}React Form vs react-hook-form
less code, easier inputs
// react-hook-form 사용 전
const [userName, setUserName] = useState('');
const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setUserName(event.target.value);
};
<input
value={userName}
onChange={handleUsernameChange}
type="text"
placeholder="Username"
required
minLength={5}
/>;
// react-hook-form 사용 후
const { register } = useForm();
<input
{...register('userName')}
type="text"
placeholder="Username"
required
minLength={5}
/>;don’t deal with events
// react-hook-form 사용 전
const [userName, setUserName] = useState("");
const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setUserName(event.target.value);
};
<input
value={userName}
onChange={handleUsernameChange}
type="text"
placeholder="Username"
required
minLength={5}
/>
<div>{`${userName}${email}${password}`}</div>
const { register } = useForm();
const { userName = "", email = "", password = "" } = watch();
<input
{...register("userName")}
type="text"
placeholder="Username"
required
minLength={5}
/>
<input
{...register("userName")}
type="text"
placeholder="Username"
required
minLength={5}
/>
<div>{`${userName}${email}${password}`}</div>better validation
// react-hook-form 사용 전
const [userName, setUserName] = useState("");
const [error, setError] = useState("");
const userNameRef = useRef<HTMLInputElement>(null);
const handleSubmit = (event: React.ChangeEvent<HTMLFormElement>) => {
event.preventDefault();
setError("");
// userName 체크
if (!userName || userName.length < 5) {
if (!userName) {
setError("userName is Required");
} else {
setError("userName 길이가 5보다 커야합니다.");
}
userNameRef.current?.focus();
}
};
<form onSubmit={handleSubmit}>
<input
value={userName}
onChange={handleUsernameChange}
type="text"
placeholder="Username"
ref={userNameRef}
/>
{error && <span>{error}</span>}
// react-hook-form 사용 후
interface formTypes {
userName: string;
email: string;
password: string;
errors?: string;
}
const {
register,
watch,
handleSubmit,
setError,
resetField,
formState: { errors: stateErrors },
} = useForm<formTypes>();
const handleInvalid = (errors: FieldErrors) => {
const { userName, email, password } = errors;
resetField("errors");
if (userName) {
setError("errors", { message: userName.message });
} else if (email) {
setError("errors", { message: email.message });
} else {
setError("errors", { message: password.message });
}
};
<form onSubmit={handleSubmit(handleValid, handleInvalid)}>
<input
{...register("userName", {
required: "userName is Required",
minLength: {
message: "userName 길이가 5보다 커야합니다.",
value: 5,
},
})}
type="text"
placeholder="Username"
/>
<span>{stateErrors.errors?.message}</span>better error (better error handling: set, clear)
// react-hook-form 사용 전
const [error, setError] = useState('');
//// set
setError('email is Required');
//// clear
setError('');
{
error && <span>{error}</span>;
}
// react-hook-form 사용 후
const {
...setError,
clearErrors,
formState: { errors: stateErrors },
} = useForm<formTypes>();
const handleInvalid = (errors: FieldErrors) => {
const { userName, email, password } = errors;
clearErrors('errors');
if (userName) {
setError('errors', { message: userName.message });
} else if (email) {
setError('errors', { message: email.message });
} else {
setError('errors', { message: password.message });
}
};
<span>{stateErrors.errors?.message}</span>;have control easier inputs event (easier event control)
// react-hook-form 사용 전
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setError("");
// userName 체크
if (!userName || userName.length < 5) {
if (!userName) {
setError("userName is Required");
} else {
setError("userName 길이가 5보다 커야합니다.");
}
userNameRef.current?.focus();
} else if (!email) {
setError("email is Required");
emailRef.current?.focus();
} else if (!password) {
setError("password is Required");
passwordRef.current?.focus();
} else {
// 성공
setUserName("");
setEmail("");
setPassword("");
setError("");
}
};
<input
value={userName}
onChange={handleUsernameChange}
onBlur={handleBlur}
type="text"
placeholder="이름을 입력하세요."
ref={userNameRef}
/>
<input
value={email}
onChange={handleEmailChange}
onBlur={handleBlur}
type="email"
ref={emailRef}
placeholder="이메일을 입력하세요."
/>
<input
value={password}
onChange={handlePasswordChange}
onBlur={handleBlur}
type="password"
ref={passwordRef}
placeholder="패스워드를 입력하세요."
/>
{error && <span>{error}</span>}
// react-hook-form 사용 후
const {
register,
watch,
handleSubmit,
setError,
reset,
clearErrors,
formState: { errors: stateErrors },
} = useForm<formTypes>({
mode: "onBlur",
});
useEffect(() => {
const { userName, email, password } = stateErrors;
clearErrors("errors");
if (userName) {
setError("errors", { message: userName.message });
} else if (email) {
setError("errors", { message: email.message });
} else {
setError("errors", { message: password?.message });
}
}, [stateErrors.email, stateErrors.password, stateErrors.userName]);
<span>{stateErrors.errors?.message}</span>Uncontrolled component vs Controlled component
Uncontrolled component
- Instead of writing an event handler for every state update, it uses a ref to pull form values from the DOM and process them.
- PULL the value
class Form extends React.Component {
handleSubmitClick = () => {
const name = this._name.value;
// do something with name..
}
render() {
return (
<div>
<input type="text" ref={input => this._name = input} />
<button onClick={this.handleSubmitClick}>Sign up</button>
</div>
);
}Controlled component
- An approach that turns the information currently entered into an HTML element into state, and updates the HTML element's value based on that changed state.
- PUSH the value
- The material ui library sometimes passes a Controlled Component that does not control the ref attribute.
class Form extends Component {
constructor() {
super();
this.state = {
name: '',
};
}
handleNameChange = event => {
this.setState({ name: event.target.value });
};
render() {
return (
<div>
<input
type="text"
value={this.state.name}
onChange={this.handleNameChange}
/>
</div>
);
}
}register vs controller usage example
-
register vs controller usage example Reference page: https://react-hook-form.com/kr/advanced-usage
// register(Uncontrolled component) const { register, } = useForm<formTypes>({ defaultValues: { userName: "", }, }); <form onSubmit={handleSubmit(handleValid, handleInvalid)}> <input {...register("userName", { required: "userName is Required", minLength: { message: "userName 길이가 5보다 커야합니다.", value: 5, }, })} type="text" placeholder="이름을 입력하세요." /> <input type="submit" /> </form> <div>{`${userName}`}</div> // controller(Controlled component) import Input from "@material-ui/core/Input"; const { control, handleSubmit, watch } = useForm<formTypes>({ defaultValues: { name: "", }, }); <form onSubmit={handleSubmit(handleValid, handleInvalid)}> <Controller name="name" control={control} rules={ required: "name is Required", minLength: { message: "name 길이가 5보다 커야합니다.", value: 5, }, } render={({ field }) => { console.log(field); return <Input {...field} />; }} /> <input type="submit" /> </form> <div>{`${name}`}</div>
References
https://so-so.dev/react/form-handling/
https://blog.toycrane.xyz/react에서-form-쉽게-다루기-b3b192cf2b33
https://defineall.tistory.com/921
https://eastflag.co.kr/react/cra/controlled-component/
https://soldonii.tistory.com/145