[TIL] 0623 | React with Redux, Next.js, TypeScript

Teasanยท2022๋…„ 6์›” 23์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
16/36
post-thumbnail

๋ชฉ์ฐจ

  • ์ „์ฒด ์–‘์‹ ์œ ํšจ์„ฑ ๊ด€๋ฆฌํ•˜๊ธฐ
  • ์‚ฌ์šฉ์ž ์ง€์ • ์ž…๋ ฅ ํ›… ์ถ”๊ฐ€ํ•˜๊ธฐ

โœง ์ „์ฒด ์–‘์‹ ์œ ํšจ์„ฑ ๊ด€๋ฆฌํ•˜๊ธฐ

  • ์šฐ๋ฆฌ๊ฐ€ ์ง€๊ธˆ๊นŒ์ง€ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•œ ๋กœ์ง๋“ค์„ ์ž‘์„ฑํ–ˆ๊ณ , ๊ฝค๋‚˜ ๋งŒ์กฑ์Šค๋Ÿฝ๊ฒŒ ๋กœ์ง์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™๋˜๋Š” ๊ฒƒ๊นŒ์ง€ ํ™•์ธํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๊ฐ€ ์žŠ์ง€ ๋ง์•„์•ผ ํ•  ์‚ฌ์‹ค์ด ์žˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ ๊ฒƒ์€ ๊ฒจ์šฐ ์ „์ฒด ํผ ์ค‘์— ํ•˜๋‚˜์˜ input ๊ฐ’์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด๋ผ๋Š” ์‚ฌ์‹ค์ด๋‹ค. ์ง€๊ธˆ ์šฐ๋ฆฌ์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํ•˜๋‚˜์˜ input ์ฐฝ์œผ๋กœ ํ•˜๋‚˜์˜ ์ž…๋ ฅ ๊ฐ’์„ ๋ฐ›๊ณ  ์žˆ์ง€๋งŒ ๋งŽ์€ ๊ฒฝ์šฐ์—๋Š” ํผ์—์„œ ๋‹ค์–‘ํ•œ ์ž…๋ ฅ ๊ฐ’์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.

  • ์ผ๋‹จ ๋จผ์ € ์ง€๊ธˆ ์šฐ๋ฆฌ๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” SimpleInput ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ดํŽด๋ณด๋ฉด, ์—ฌ๊ธฐ์—์„œ ์ „์ฒด ํผ์ด ์œ ํšจํ•œ์ง€ ์•„๋‹Œ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ๋” ์ข‹์„ ๊ฒƒ ์ด๋‹ค.

์ „์ฒด ํผ์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆํ•˜๊ธฐ

  • ์ž…๋ ฅ ๊ฐ’์ด ํ•˜๋‚˜๋งŒ ์žˆ์„ ๋•Œ๋Š” ํ•˜๋‚˜๋งŒ ์œ ํšจํ•˜๋‹ค๋ฉด ์ „์ฒด ํผ์ด ์œ ํšจํ•œ ๊ฒƒ์ด์ง€๋งŒ, ๋งŒ์•ฝ ์—ฌ๋Ÿฌ ์ž…๋ ฅ ๊ฐ’์ด ์žˆ๊ณ  ์ด์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ์ €๋งˆ๋‹ค ๋‹ค๋ฅด๋‹ค๋ฉด ์ „์ฒด ํผ์€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ „์ฒด ํผ์ด ์œ ํšจํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ชจ๋“  ์ž…๋ ฅ ๊ฐ’์ด ์œ ํšจํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋”ฐ๋ผ์„œ ํ•˜๋‚˜์˜ ์ž…๋ ฅ ๊ฐ’์ด๋ผ๋„ ์œ ํšจํ•˜์ง€ ์•Š์€ ์ˆœ๊ฐ„ ์ „์ฒด ํผ์€ ์œ ํšจํ•˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌํ•ด์ค˜์•ผ ํ•œ๋‹ค.
const [formIsValid, setFormIsValid] = useState(false);
  • ํ•œ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์€ formIsValid ์ƒํƒœ(state)๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ดˆ๊ธฐ ๊ฐ’์€ false๋กœ ์ง€์ •ํ•ด๋†“๊ณ , formIsValid ์ƒํƒœ์˜ ๊ฐ’์„ ํผ์— ์žˆ๋Š” input ๊ฐ’์ด ๋ณ€ํ™”ํ•  ๋•Œ๋งˆ๋‹ค ์—…๋ฐ์ดํŠธ ๋˜๋„๋ก ํ•ด์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์œ„ํ•ด์„œ ๋‹ค์‹œ useEffect๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.
const enteredNameIsValid = enteredName.trim() !== "";

useEffect(() => {
  if (enteredNameIsValid) {
  }
}, [enteredNameIsValid]);
  • useEffect๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ „์ฒด ํผ์˜ ์œ ํšจ์„ฑ์„ ์„ค์ •ํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ํผ์˜ ์ž…๋ ฅ ๊ฐ’์˜ ์œ ํšจ์„ฑ์ด ํ•„์š”ํ•˜๋ฏ€๋กœ, ํผ์— ์žˆ๋Š” ๋ชจ๋“  ์ž…๋ ฅ ๊ฐ’์˜ ์œ ํšจ์„ฑ์„ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•˜๋„๋ก ํ•œ๋‹ค.
useEffect(() => {
  if (enteredNameIsValid) {
    setFormIsValid(true);
  } else {
    setFormIsValid(false);
  }
}, [enteredNameIsValid]);
  • enteredNameIsValid ์™€ if ๋ฌธ์„ ์‚ฌ์šฉํ•ด์„œ ์ถ”๊ฐ€ํ•œ๋‹ค. ๋งŒ์•ฝ ํผ ์•ˆ์— ์ž…๋ ฅ ๊ฐ’์ด ๋‘๊ฐœ๋ผ๋ฉด ์˜์กด์„ฑ ๋ฐฐ์—ด๊ณผ if ๋ฌธ ์•ˆ์—์„œ ๋˜ ๋‹ค๋ฅธ ์ƒ์ˆ˜ ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋  ๊ฒƒ์ด๋‹ค. ์•ž์„œ ๋ฐฐ์› ๋˜ ๋Œ€๋กœ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ๋“ค์–ด๊ฐ€๋Š” ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค useEffect๋Š” ๋‹ค์‹œ ์‹คํ–‰๋  ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ useEffect ํ˜ธ์ถœ์—์„œ๋Š” ๋ชจ๋“  ์˜์กด์„ฑ ๊ฐ’๋“ค์„ ํ•ฉ์นœ ๋’ค์— ์ด ๊ฐ’์ด ๋ชจ๋‘ ์œ ํšจํ•œ์ง€๋ฅผ ํ™•์ธํ•˜๊ณ , ๋งŒ์•ฝ ์ž…๋ ฅ ๊ฐ’ ๋ชจ๋‘๊ฐ€ ์œ ํšจํ•˜๋‹ค๋ฉด ์ „์ฒด ํผ ๋˜ํ•œ ์œ ํšจํ•˜๋‹ค(setFormIsValid(true))๊ณ  ์„ค์ •ํ•ด์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•˜๋‚˜๋ผ๋„ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด, ์ „์ฒด ํผ ๋˜ํ•œ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค(setFormIsValid(false))๊ณ  ์„ค์ •ํ•ด์ค€๋‹ค.

formIsValid ์ƒํƒœ(state)๋ฅผ ์ด์šฉํ•œ ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™”

  • ์ด์ œ formIsValid ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, ๋ฒ„ํŠผ์„ formIsValid์˜ ์ƒํƒœ ๊ฐ’์— ๋”ฐ๋ผ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ด๋‹ค.
<button disabled={!formIsValid}>Submit</button>
  • ๋งŒ์•ฝ formIsValid์ด false ๋ผ๋ฉด(ํผ์ด ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด), ๋ฒ„ํŠผ ํƒœ๊ทธ์— disabled ๋ผ๋Š” ์†์„ฑ์ด ์ž‘๋™๋  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

index.css

button:disabled,
button:disabled:hover,
button:disabled:active {
  background-color: #ccc;
  color: #292929;
  border-color: #ccc;
  cursor: not-allowed;
}
  • ์ด๋ฅผ ๊ฐ€์‹œํ™”ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ ๋ฒ„ํŠผ์ด disabled ๋˜์—ˆ์„ ๋•Œ์˜ ์Šคํƒ€์ผ๋ง๋„ ์„ค์ •ํ•ด์ค€๋‹ค.

  • ์ด์ œ ์‹œ์ž‘ํ•  ๋•Œ ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™” ๋˜์–ด ์žˆ๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋นˆ์นธ์œผ๋กœ๋งŒ ๋‘์–ด๋„ ๋น„ํ™œ์„ฑํ™” ๋˜์–ด ์žˆ์ง€๋งŒ ์œ ํšจํ•œ ๊ธ€์ž๋ฅผ ํ•˜๋‚˜๋ผ๋„ ์ž…๋ ฅํ•˜๋ฉด ๋‹ค์‹œ ๋ฒ„ํŠผ์ด ํ™œ์„ฑํ™”๋˜๊ณ , ๊ธ€์ž๋ฅผ ๋ชจ๋‘ ์ง€์šฐ๊ณ  ๋นˆ์นธ์œผ๋กœ ๋‘๋ฉด ๋‹ค์‹œ ๋ฒ„ํŠผ์€ ๋น„ํ™œ์„ฑํ™” ๋œ๋‹ค.

๊ฐ’์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ํผ์ด ์ œ์ถœ๋  ์ˆ˜๋„ ์—†๋‹ค.

  • ์ด์ œ๋Š” ๊ฐ’์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด, ํผ์„ ์ œ์ถœํ•  ์ˆ˜๋„ ์—†๊ฒŒ ๋˜์—ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ ๋ฒ„ํŠผ์˜ ๋น„ํ™œ์„ฑํ™” ์—ฌ๋ถ€๋Š” ์„ ํƒ์— ๋‹ฌ๋ ค์žˆ๋‹ค. ์ด ์‹œ์ ์— ๋Œ€ํ•ด ์˜๊ฒฌ์ด ๋ถ„๋ถ„ํ•˜๋ฉฐ, ์–ด๋–ค ์‚ฌ๋žŒ๋“ค์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌด์—‡์„ ์ž…๋ ฅํ•ด์•ผํ•  ์ง€๋„ ๋ชจ๋ฅด๋Š” ์ƒํƒœ์—์„œ ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฐ’์ด๋ผ๋„ ์ œ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ฃผ์žฅํ•˜๊ธฐ๋„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋‹ค๋งŒ ์ด๊ฒƒ์€ ์„ ํƒ์˜ ๋ฌธ์ œ์ด๋ฉฐ, ์–ธ์ œ๋“  ๋ชฉ์ ๊ณผ ์š”๊ตฌ์— ๋”ฐ๋ผ ์ด ์‹œ์ ์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

useEffect๋ฅผ ๊ผญ ์‚ฌ์šฉํ•ด์•ผ๋งŒ ํ• ๊นŒ?

useEffect(() => {
  if (enteredNameIsValid) {
    setFormIsValid(true);
  } else {
    setFormIsValid(false);
  }
}, [enteredNameIsValid]);
  • ์‚ฌ์‹ค ์ด ๋กœ์ง์„ ์กฐ๊ธˆ๋งŒ ๋” ์‚ดํŽด๋ณด๋ฉด ์ด๋Š” ์–ด๋– ํ•œ ํšจ๊ณผ(effect)๋„ ์—†๊ธฐ ๋•Œ๋ฌธ์— useEffect๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ useEffect ์—†์ด๋„ ๊ทธ ์–ด๋–ค ๋ฌธ์ œ๋„ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์‚ฌ์‹ค๋„ ๋ง์ด๋‹ค.
const enteredNameIsValid = enteredName.trim() !== "";
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
  • ๋”ฐ์ง€๊ณ ๋ณด๋ฉด useEffect ๋‚ด๋ถ€์—์„œ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๋Š” ํ˜„ ๋กœ์ง๋“ค์€ ์œ„์˜ enteredNameIsValid ๋‚˜ nameInputIsInvalid์™€ ๊ฑฐ์˜ ๋™์ผํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐ’์„ ์–ป๊ณ  ์žˆ๋‹ค. ์ฆ‰ ์™„์ „ํžˆ ๊ฐ™์€ ์ž‘์—…์ด๋ผ ๋งํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, ๋‹ค๋งŒ useEffect ๋‚ด๋ถ€์˜ ๋กœ์ง๋“ค์€ ํผ ์ „์ฒด์— ๋Œ€ํ•œ ๋กœ์ง์ด๋ผ๋Š” ์ฐจ์ด๊ฐ€ ์žˆ์„ ๋ฟ์ด๋‹ค. ๊ฒฐ๋ก ์ ์œผ๋กœ ์ง€๊ธˆ useEffect๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ์ด๋“์€ ์—†์œผ๋ฉฐ, ์žฌํ‰๊ฐ€๋ฅผ ํ•  ๋•Œ ์ถ”๊ฐ€์ ์ธ ์ปดํฌ๋„ŒํŠธ๋งŒ ์ƒ๊ธธ ๋ฟ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋Š” ํ™•์‹คํžˆ ์†ํ•ด์ด๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋Š” ๋Œ€์‹  ์šฐ๋ฆฌ๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.
const [formIsValid, setFormIsValid] = useState(false);

useEffect(() => {
  if (enteredNameIsValid) {
    setFormIsValid(true);
  } else {
    setFormIsValid(false);
  }
}, [enteredNameIsValid]);
  • formIsValid ์ƒํƒœ(state)์™€ useEffect๋ฅผ ์ œ๊ฑฐํ•˜๊ณ ,
let formIsValid = false;

if (enteredNameIsValid) {
  formIsValid = true;
} else {
  formIsValid = false;
}
  • ๋‹จ์ˆœํžˆ formIsValid ์ด๋ผ๋Š” ๋™์ผํ•œ ์ด๋ฆ„์˜ ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ๊ธฐ๋ณธ๊ฐ’์„ false๋กœ ๋‘” ๋’ค, if ๋ฌธ ๋‚ด๋ถ€์— ๋‹จ์ˆœํ•˜๊ฒŒ ์กฐ๊ฑด๋ฌธ์ด ์ฐธ์ด๋ฉด true๋กœ ํ• ๋‹นํ•˜๊ณ , ์กฐ๊ฑด๋ฌธ์ด ๊ฑฐ์ง“์ด๋ฉด false ๋กœ ํ• ๋‹นํ•˜๋„๋ก ์ž‘์—…ํ•ด์ฃผ์—ˆ๋‹ค.
let formIsValid = false;

if (enteredNameIsValid) {
  formIsValid = true;
}
  • ์‚ฌ์‹ค else ๋ฌธ๋„ ํ•„์š” ์—†๋‹ค. ๋ชจ๋“  ๊ฐ’์ด true ์ผ ๋•Œ์—๋งŒ formIsValid ๋ณ€์ˆ˜๊ฐ€ true ์ด๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด์ œ ์ฝ”๋“œ๋Š” ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ ์ด์ „ ๋ณด๋‹ค ๊ฐ„๊ฒฐํ•ด์กŒ๊ณ  useEffect๋ฅผ ํ†ตํ•œ ์“ธ๋ฐ ์—†๋Š” ๋‚ญ๋น„๊ฐ€ ์‚ฌ๋ผ์ง€๋ฉฐ ํ•œ๊ฒฐ ๊ฐ€๋ฒผ์›Œ์กŒ๋‹ค.

โœง ์‚ฌ์šฉ์ž ์ง€์ • ์ž…๋ ฅ ํ›… ์ถ”๊ฐ€ํ•˜๊ธฐ

SimpleInput.js

const [enteredName, setEnteredName] = useState("");
const [enteredNameTouched, setEnteredNameTouched] = useState(false);

const [enteredEmail, setEnteredEmail] = useState("");
const [enteredEmailTouched, setEnteredEmailTouched] = useState(false);

const enteredNameIsValid = enteredName.trim() !== "";
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;

const enteredEmailIsValid =
  enteredEmail.trim() !== "" && enteredEmail.includes("@");
const emailInputIsInvalid = !enteredEmailIsValid && enteredEmailTouched;

let formIsValid = false;

if (enteredNameIsValid && enteredEmailIsValid) {
  formIsValid = true;
} else {
  formIsValid = false;
}

const nameInputChangeHandler = (event) => {
  setEnteredName(event.target.value);
};

const emailInputChangeHandler = (event) => {
  setEnteredEmail(event.target.value);
};

const nameInputBlurHandler = () => {
  setEnteredNameTouched(true);
};

const emailInputBlurHandler = () => {
  setEnteredEmailTouched(true);
};

const formSubmitssionHandler = (event) => {
  event.preventDefault();
  setEnteredNameTouched(true);
  setEnteredEmailTouched(true);

  if (!enteredNameIsValid || !enteredEmailIsValid) {
    return;
  }

  setEnteredName("");
  setEnteredNameTouched(false);

  setEnteredEmail("");
  setEnteredEmailTouched(false);
};

const nameInputClasses = nameInputIsInvalid // true ์ด๋ฉด,
  ? "form-control invalid" // ๊ฒฝ๊ณ  css
  : "form-control";

const emailInputClasses = emailInputIsInvalid // true ์ด๋ฉด,
  ? "form-control invalid" // ๊ฒฝ๊ณ  css
  : "form-control";
  • ํ˜„์žฌ SimpleInput ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™์ด ๋˜๊ณ  ์žˆ์ง€๋งŒ ์ค‘๋ณต๋˜๋Š” ๋กœ์ง์ด ๋งŽ์€ ๊ฑธ ์•Œ ์ˆ˜๊ฐ€ ์žˆ๋‹ค. ๋ฌผ๋ก  ์ƒ์ˆ˜ ์ด๋ฆ„๊ณผ, ๋กœ์ง์˜ ๋””ํ…Œ์ผ์€ ๋‹ค๋ฅด์ง€๋งŒ ์ „๋ฐ˜์ ์ธ ๋กœ์ง์˜ ๊ตฌ์กฐ๋Š” ์‚ฌ์‹ค์ƒ ์™„์ „ํžˆ ๊ฐ™์€ ๊ฒƒ์ด๋‚˜ ๋‹ค๋ฆ„ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋งŒ์•ฝ ์ตœ์†Œ ์„ธ๊ฐœ์˜ input์ด ์žˆ์„ ๋•Œ ์ตœ์ข…์ ์œผ๋กœ๋Š” ๋˜‘๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ ์ฝ”๋“œ๋ฅผ ์„ธ ๋ฒˆ์ด๋‚˜ ๋ฐ˜๋ณตํ•ด์•ผ๋งŒ ํ•  ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด, ์ด๋Ÿฐ ์ค‘๋ณต๋œ ์ฝ”๋“œ๋“ค์„ ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ค„์ผ ์ˆ˜ ์žˆ์„๊นŒ? ๋ฌผ๋ก , input ์— ๋Œ€ํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ด€๋ จ๋œ ๋กœ์ง๋“ค์„ ๊ทธ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ปดํฌ๋„ŒํŠธ ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง๊ณผ ์ƒํƒœ(state)๋ฅผ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๊ฝค ๊ดœ์ฐฎ์„์ง€๋„ ๋ชจ๋ฅธ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฒƒ๋„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ„๋ฆฌํ–ˆ์„ ๋ฟ์ด์ง€, ๋ณต์žกํ•œ ์ „์ฒด์ ์ธ ํผ์„ ๊ด€๋ฆฌํ•˜๊ธฐ์—๋Š” ์–ด๋”˜๊ฐ€ ์„์—ฐ์น˜ ์•Š๋‹ค.

์ „์ฒด ํผ์˜ ์œ ํšจ์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ํ•ด๊ฒฐ์ฑ…์ด๋‹ค

  • ์šฐ๋ฆฌ์˜ ํ•ด๊ฒฐ ํฌ์ธํŠธ๋Š” ์ „์ฒด ํผ์˜ ์œ ํšจ์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์— ์žˆ๋‹ค. ์ „์ฒด ํผ์˜ ์œ ํšจ์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊นŒ๋‹ค๋กœ์šด ์ด์œ ๋Š” ๋ชจ๋“  ์ž…๋ ฅ์„ ๊ฐœ๋ณ„์ ์ธ ๊ฒƒ์œผ๋กœ ๋‹ค๋ฃจ๋Š”๋ฐ ๊ฐ๊ฐ์˜ ์ž…๋ ฅ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ์„ ์ฒดํฌํ•˜๋ฉด์„œ๋„ ์ „์ฒด ํผ์ด ์œ ํšจํ•œ์ง€ ์•Œ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋งŒ์•ฝ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ๊ด€๋ฆฌํ•œ๋‹ค๋ฉด, ์ด๋Š” prop์„ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ์—์„œ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๊ฒฐ๋  ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ณด๋‹ค ๋” ๋‚˜์€ ๋ฐฉ์‹์ด ์žˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ์ปค์Šคํ…€ ํ›…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ปค์Šคํ…€ ํ›…์„ ์ด์šฉํ•ด์„œ ์ƒํƒœ์— ๊ด€๋ จ๋œ ๋ชจ๋“  ๋กœ์ง์„ ๊ด€๋ฆฌํ•˜์ž

  • ์šฐ๋ฆฌ๊ฐ€ ์•ž์„œ ์ปค์Šคํ…€ ํ›…์„ ํ•™์Šตํ–ˆ์„ ๋•Œ๋ฅผ ๊ธฐ์–ตํ•ด๋ณด์ž. ์ปค์Šคํ…€ ํ›…์„ ์ด์šฉํ•œ๋‹ค๋ฉด, ์ƒํƒœ์— ๊ด€๋ จ๋œ ๋กœ์ง์„ ์•„์›ƒ์†Œ์‹ฑ ํ•ด์„œ ์ปค์Šคํ…€ ํ›…์„ import ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋กœ์ง์€ ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ปค์Šคํ…€ ํ›… ์ƒ์„ฑํ•˜๊ธฐ

use-input.js

const useInput = () => {};

export default useInput;
  • ๋จผ์ € hooks ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ , use-input.js๋ผ๋Š” ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค. ์ด use-input.js ํŒŒ์ผ์€ ์ƒํƒœ(state)๋ฅผ ๋‹ค๋ฃจ๋Š” ํ›…๊ณผ input์— ๋Œ€ํ•œ ๋กœ์ง์„ ๋‹ด์„ ๊ฒƒ์ด๋‹ค. ํŒŒ์ผ ์•ˆ์— useInput ์ด๋ผ๋Š” ํ›…์„ ๋งŒ๋“ค๊ณ , ์™ธ๋ถ€์—์„œ import ํ•ด์˜ฌ ์ˆ˜ ์žˆ๋„๋ก export ๋„ ํ•ด์ค€๋‹ค. useInput ์ด๋ผ๋Š” ์ปค์Šคํ…€ ํ›…์„ ์ด์šฉํ•ด์„œ input ๊ฐ’๊ณผ input ์ฐฝ์ด touched ๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ƒํƒœ๋ฅผ ๋‹ค๋ฃฐ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๋‘˜์„ ์กฐํ•ฉํ•ด ์œ ํšจ์„ฑ ๋˜ํ•œ ๊ฒ€์ฆํ•  ๊ฒƒ์ด๋‹ค. ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์€ ์œ ์—ฐํ•˜๊ฒŒ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์™ธ๋ถ€์—์„œ ์ •ํ™•ํ•œ ๊ฒ€์ฆ ๋กœ์ง์„ ์ปค์Šคํ…€ ํ›…์—์„œ ์ „๋‹ฌ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ๋งŒ ํ•œ๋‹ค.
import { useState } from "react";

const useInput = () => {
  const [enteredName, setEnteredName] = useState("");
  const [enteredNameTouched, setEnteredNameTouched] = useState(false);
};
  • ๋จผ์ €, SimpleInput ์ปดํฌ๋„ŒํŠธ์—์„œ name์— ๋Œ€ํ•œ input ์ƒํƒœ(state)๋ฅผ ๊ด€๋ฆฌํ•ด์ฃผ๋˜ ์ƒํƒœ๋“ค์„ ๋ณต์‚ฌํ•ด์„œ ๊ธ์–ด์˜จ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์ƒํƒœ๋“ค์€ ์œ ์—ฐํ•˜๊ฒŒ ์ž‘๋™๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ธˆ ๋” ํฌ๊ด„์ ์ด๊ณ  ์ผ๋ฐ˜์ ์ธ ์ด๋ฆ„์œผ๋กœ ์ˆ˜์ •ํ•ด์ค€๋‹ค.
const useInput = () => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  // const enteredNameIsValid = enteredName.trim() !== "";
  // const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
  const valueIsValid = enteredName.trim() !== "";
  const hasError = !valueIsValid && isTouched;
};
  • ์œ ํšจ์„ฑ์— ๋Œ€ํ•œ ๊ฐ’๋“ค์ธ enteredNameIsValid์™€ nameInputIsInvalid๋„ SimpleInput ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ธ์–ด์™€ ์ด์ „์˜ ๋ฐฉ์‹์ฒ˜๋Ÿผ ์ผ๋ฐ˜์ ์ธ ์ด๋ฆ„์œผ๋กœ ์ˆ˜์ •ํ•ด์ค€๋‹ค.
const useInput = (validateValue) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  // const valueIsValid = enteredName.trim() !== "";
  const valueIsValid = validateValue();
  const hasError = !valueIsValid && isTouched;
};
  • valueIsValid ๊ฐ™์€ ๊ฒ€์ฆ ๋กœ์ง์˜ ๊ฒฝ์šฐ ํ•˜๋“œ ์ฝ”๋”ฉ์„ ์ง€์–‘ํ•ด์•ผ ํ•˜๊ณ (์™ธ๋ถ€์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์— ๋Œ€ํ•œ ์ •ํ™•ํ•œ ๋กœ์ง์„ ๋ฐ›์•„์™€์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—.) ํ›…์ด ์‚ฌ์šฉ๋˜๋Š” ๊ณณ์—์„œ ์–ด๋–ค ๊ฒ€์ฆ ๋กœ์ง์„ ์‚ฌ์šฉํ• ์ง€ ๊ฐ€์ ธ์™€์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— useInput ์ปค์Šคํ…€ ํ›…์—์„œ validateValue ์ด๋ผ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜(ํ•จ์ˆ˜๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค)๋ฅผ ๋ฐ›์•„์™€ ํ˜ธ์ถœํ•˜๋„๋ก ํ•œ๋‹ค.
const useInput = (validateValue) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  const valueIsValid = validateValue(validateValue);
  const hasError = !valueIsValid && isTouched;
};
  • ๊ทธ๋ฆฌ๊ณ  ์™ธ๋ถ€์˜ ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜ validateValue ํ•จ์ˆ˜ ์•ˆ์— enteredValue๋ฅผ ์ž…๋ ฅํ•ด ์‹คํ–‰ํ•œ ๊ฐ’์ด ๋  ์ˆ˜ ์žˆ๋„๋ก ์ž‘์„ฑํ•ด์ค€๋‹ค.
const useInput = (validateValue) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  const valueIsValid = validateValue(enteredValue);
  const hasError = !valueIsValid && isTouched;

  return {
    value: enteredValue,
    hasError: hasError, // hasError ํ•˜๋‚˜๋งŒ ์จ๋„ ๋œ๋‹ค.
  };
};
  • ์ด์ œ useInput ์ปค์Šคํ…€ ํ›…์€ ๋ฌด์–ธ๊ฐ€๋ฅผ return(๋ฐ˜ํ™˜) ํ•ด์ค˜์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Š” ๊ฐ์ฒด๊ฐ€ ๋  ์ˆ˜๋„ ๋ฐฐ์—ด์ด ๋  ์ˆ˜๋„ ์žˆ์Œ์„ ๊ธฐ์–ตํ•˜์ž. ์–ด์จŒ๋“  ํ˜„์žฌ๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ฐ’์„ ์ปค์Šคํ…€ ํ›…์—์„œ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๊ณ , ์—ฌ๊ธฐ์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ์ฒด ์•ˆ์—๋Š” ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ๋“ค์˜ ์ด๋ฆ„์„ ํ‚ค, ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’๋“ค์„ ๊ฐ’์œผ๋กœ ๋„ฃ๊ณ  ์™ธ๋ถ€์—์„œ ๊ทธ ํ‚ค ๊ฐ’์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. ๋ฌผ๋ก , ๊ฐ™์€ ์ด๋ฆ„์„ ํ‚ค์™€ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋ชจ๋˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋ฌธ๋ฒ•์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์‚ฌ์šฉํ•ด๋„ ๋™์ผํ•œ ์ž‘๋™์›๋ฆฌ๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค. ์ด์ œ ์™ธ๋ถ€์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์˜ setEnteredValue์™€ setIsTouched๋ฅผ ์‚ฌ์šฉํ•  ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•˜๋‹ค.
import { useState } from "react";

const useInput = (validateValue) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  const valueIsValid = validateValue(enteredValue);
  const hasError = !valueIsValid && isTouched;

  // const nameInputChangeHandler = (event) => {
  //   setEnteredName(event.target.value);
  // };
  const valueChangeHandler = (event) => {
    setEnteredValue(event.target.value);
  };

  // const nameInputBlurHandler = () => {
  //   setEnteredNameTouched(true);
  // };
  const inputBlurHandler = () => {
    setIsTouched(true);
  };

  return {
    value: enteredValue,
    hasError: hasError,
  };
};
  • ์ด๋ฅผ ์œ„ํ•ด์„œ, SimpleInput ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋˜ nameInputChangeHandler์™€ nameInputBlurHandler ํ•จ์ˆ˜๋ฅผ ๊ทธ๋Œ€๋กœ ๊ธ์–ด์™€ ๋ถ™์—ฌ ๋„ฃ์–ด์ค€๋‹ค. SimpleInput ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜์˜ ์ด๋ฆ„์€ ์ปค์Šคํ…€ ํ›…์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ๊ธฐ์— ์ด ๋ถ€๋ถ„๋„ ์ˆ˜์ •ํ•ด์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด์ „์ฒ˜๋Ÿผ ์กฐ๊ธˆ ๋” ์ผ๋ฐ˜์ ์ธ ์ด๋ฆ„ valueChangeHandler, inputBlurHandler ์œผ๋กœ ์ˆ˜์ •ํ•œ๋‹ค.
return {
  value: enteredValue,
  hasError: hasError,
  valueChangeHandler: valueChangeHandler,
  inputBlurHandler: inputBlurHandler,
};
  • ๋ฌผ๋ก , ์™ธ๋ถ€ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์˜ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜ํ™˜(return)ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์œผ๋ฉด ์•ˆ๋  ๊ฒƒ์ด๋‹ค. ์ด๋ ‡๊ฒŒ ์ปค์Šคํ…€ ํ›…์—์„œ ์ •์˜๋œ ํ•จ์ˆ˜๋“ค์€ ์ปค์Šคํ…€ ํ›…์„ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

์ปค์Šคํ…€ ํ›… ์‚ฌ์šฉํ•˜๊ธฐ

  • useInput ์ปค์Šคํ…€ ํ›…์„ ์‚ฌ์šฉํ•  ์ปดํฌ๋„ŒํŠธ์ธ SimpleInput ์ปดํฌ๋„ŒํŠธ๋กœ ์ด๋™ํ•˜์—ฌ, import ํ•ด์ค€๋‹ค.
import useInput from "../hooks/use-input";

const SimpleInput = (props) => {
  const {} = useInput();
  ...
};
  • useInput๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ์ด์ œ ๊ฐ์ฒด ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น์„ ์‚ฌ์šฉํ•ด์„œ ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์—์„œ ๊ฐ์ฒด ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฐ’๋“ค์„ ์ถ”์ถœํ•œ๋‹ค.
const SimpleInput = (props) => {
    const {
    value: enteredName,
    hasError: nameInputHasError,
    valueChangeHandler: nameChangeHandler,
    inputBlurHandler: nameBlurHandler,
  } = useInput();
  ...
};
  • value๋ž€ ์ด๋ฆ„์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ ๊ฐ’์— enteredName์„ ํ• ๋‹นํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  hasError๋Š” nameInputHasError๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ํ• ๋‹นํ•œ๋‹ค. (์—ฌ๊ธฐ์„œ ๊ฐ’์œผ๋กœ ๋“ค์–ด๊ฐ€๋Š” ์ด๋ฆ„์€ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ˆ, ์ง๊ด€์ ์œผ๋กœ ๋ณด์—ฌ์ง€๋„๋ก ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋” ์ข‹๋‹ค.) ๋‚˜๋จธ์ง€ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋“ค์ธ valueChangeHandler์™€ inputBlurHandler๋„ ๊ฐ๊ฐ์˜ ์‚ฌ์šฉํ•  ์ด๋ฆ„์„ ์ง€์–ด์„œ ์ž…๋ ฅํ•ด์ค€๋‹ค.
const SimpleInput = (props) => {
    const {
    value: enteredName,
    hasError: nameInputHasError,
    valueChangeHandler: nameChangeHandler,
    inputBlurHandler: nameBlurHandler,
  } = useInput();
  ...
};
  • ์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ๋ญ˜ ํ•ด์•ผ ํ• ๊นŒ? ์ด์ „์— ์šฐ๋ฆฌ๊ฐ€ useInput ์ปค์Šคํ…€ ํ›…์—์„œ ๋ฐ›์•„์˜ค๊ธฐ๋กœ ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ธฐ์–ตํ•  ๊ฒƒ์ด๋‹ค. ์™ธ๋ถ€ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์„ ์‚ฌ์šฉํ•  ๋•Œ ์–ด๋–ค ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋„˜๊ฒจ์ฃผ๊ธฐ๋กœ ์•ฝ์†ํ–ˆ์œผ๋‹ˆ, ์ด๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž.

use-input.js

const useInput = (validateValue) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  const valueIsValid = validateValue(enteredValue);
  const hasError = !valueIsValid && isTouched;
...
}

์ปค์Šคํ…€ ํ›…์— ์ „๋‹ฌํ•˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ธ๋ผ์ธ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๊ธฐ

  • ์šฐ๋ฆฌ๋Š” useInput() ์ปค์Šคํ…€ ํ›…์„ ํ˜ธ์ถœํ•˜๋ฉด์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋„˜๊ฒจ์ฃผ๊ธฐ๋กœ ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์œ„ํ•ด์„œ ์šฐ๋ฆฌ๋Š” ์ธ๋ผ์ธ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

SimpleInput.js

const SimpleInput = (props) => {
    const {
    value: enteredName,
    hasError: nameInputHasError,
    valueChangeHandler: nameChangeHandler,
    inputBlurHandler: nameBlurHandler,
  } = useInput((value) => value.trim() !== "");
  ...
};
  • useInput ์ปค์Šคํ…€ ํ›…์— ๋„˜๊ฒจ์ฃผ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์— value๋ฅผ ์ž…๋ ฅ ๋ฐ›์•„ ๋นˆ ๋ฌธ์ž์—ด์„ ๋น„๊ตํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” value.trim() !== "" ์„ ์ •์˜ํ•˜์ž. ์ด๋Š” ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„˜๊ฒจ์ค€ ๊ฒƒ์ธ๋ฐ, ์ด ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ธ๋ผ์ธ ํ•จ์ˆ˜๋กœ ์ •์˜๋งŒ ๋˜๊ณ  ์‹คํ–‰๋˜์ง€ ์•Š์œผ๋ฉฐ ๊ทธ์ € useInput์— ์ „๋‹ฌํ•  ๋ฟ์ด๋‹ค. ์ด๋Š” ์ปค์Šคํ…€ ํ›…์ธ useInput์—์„œ ์ „๋‹ฌ ๋ฐ›๊ธฐ๋กœ ํ•œ validateValue ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „ํ•ด์ง€๊ณ ,
import { useState } from "react";

const useInput = (validateValue) => {
  const [enteredValue, setEnteredValue] = useState("");
  ...
  const valueIsValid = validateValue(enteredValue);
  ...
};
  • useInput ์ปค์Šคํ…€ ํ›…์˜ ๋‚ด๋ถ€์ธ valueIsValid ๋ผ๋Š” ๋ณ€์ˆ˜์˜ ๊ฐ’์œผ๋กœ ํ• ๋‹นํ•œ validateValue()๊ฐ€ ํ˜ธ์ถœ ๋˜์—ˆ์„ ๋•Œ ๋น„๋กœ์†Œ ์ธ๋ผ์ธ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  enteredValue ์ƒํƒœ(state)๋Š” ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์—์„œ ๋‹ค๋ฃจ๋Š” ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— validateValue() ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ์„ ๋•Œ value ๊ฐ€ ๋˜์–ด ์‹คํ–‰๋œ๋‹ค.

    ์—ฌ๊ธฐ์„œ ์˜๋ฏธํ•˜๋Š” value ๋Š” useInput((value) => value.trim() !== "") ์—์„œ ์ธ๋ผ์ธ ํ•จ์ˆ˜๊ฐ€ ์ „๋‹ฌ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๋Š” value๋ฅผ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  • ์ด๋Š” ๊ฒฐ๊ตญ ํ•จ์ˆ˜๋ฅผ ๋‹ค๋ฅธ ํ•จ์ˆ˜์˜ ์ž…๋ ฅ ๊ฐ’์œผ๋กœ ๋„ฃ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋ฌธ๋ฒ•์ผ ๋ฟ์ด๋‹ค. ์ด๋ฅผ ํ†ตํ•ด์„œ ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์ด ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง์„ ๋ฐ”๊พธ๊ณ  ๊ทธ ๊ฒ€์ฆ ๋กœ์ง์„ ์ปค์Šคํ…€ ํ›… ์•ˆ์—์„œ ์‹คํ–‰๋  ์ˆ˜๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. ์œ ํšจ์„ฑ์— ๋Œ€ํ•œ ์ •๋ณด ๋˜ํ•œ ์™ธ๋ถ€ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ์—ญ์‹œ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋„๋ก ํ•œ๋‹ค.

const useInput = (validateValue) => {
  ...

  const valueIsValid = validateValue(enteredValue);
  const hasError = !valueIsValid && isTouched;

  ...

  return {
    value: enteredValue,
    isValid: valueIsValid,
    hasError: hasError,
    valueChangeHandler: valueChangeHandler,
    inputBlurHandler: inputBlurHandler,
  };
};
  • ์ž…๋ ฅ ๊ฐ’์ด ์œ ํšจํ•œ์ง€์— ๋Œ€ํ•œ ๊ฐ’ valueIsValid๋ฅผ isValid๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๊ณ ,

SimpleInput.js

const SimpleInput = (props) => {
  const {
    value: enteredName,
    isValid: enteredNameIsValid,
    hasError: nameInputHasError,
    valueChangeHandler: nameChangeHandler,
    inputBlurHandler: nameBlurHandler,
    reset: resetNameInput,
  } = useInput((value) => value.trim() !== "");

  // ์ƒํƒœ ๋กœ์ง
  // const [enteredName, setEnteredName] = useState("");
  // const [enteredNameTouched, setEnteredNameTouched] = useState(false);

  // ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง
  // const enteredNameIsValid = enteredName.trim() !== "";
  // const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;

  let formIsValid = false;

  if (enteredNameIsValid && enteredEmailIsValid) {
    formIsValid = true;
  } else {
    formIsValid = false;
  }

  // const nameInputChangeHandler = (event) => {
  //   setEnteredName(event.target.value);
  // };

  // const nameInputBlurHandler = () => {
  //   setEnteredNameTouched(true);
  // };
};
  • ๋‹ค์‹œ SimpleInput ์ปดํฌ๋„ŒํŠธ๋กœ ๋Œ์•„์™€์„œ, isValid๋ฅผ ์ถ”์ถœํ•˜์—ฌ enteredNameIsValid ๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ํ• ๋‹นํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ์ˆ˜์ •์„ ํ•ด์ฃผ๋ฉด, ํ˜„์žฌ ์ปดํฌ๋„ŒํŠธ์—์„œ name ์— ๋Œ€ํ•œ ์ƒํƒœ(state)๋ฅผ ๊ด€๋ฆฌํ•˜๋˜ ๋กœ์ง์„ ์ „๋ถ€ ์ง€์›Œ๋„ ์ „์ฒด ํผ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ์•„๋ž˜์˜ ๋กœ์ง์—์„œ enteredNameIsValid๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. ๋ฌผ๋ก  ๋‚˜๋จธ์ง€ nameInputChangeHandler์™€ nameInputBlurHandler ํ•จ์ˆ˜๋„ ์ปค์Šคํ…€ ํ›…์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋‘ ์ง€์›Œ์ค€๋‹ค.
<input
  type="text"
  id="name"
  // onChange={nameInputChangeHandler}
  // onBlur={nameInputBlurHandler}
  onChange={nameChangeHandler}
  onBlur={nameBlurHandler}
  value={enteredName}
/>
  • ๊ทธ๋ฆฌ๊ณ  ์ง€์›Œ์ง„ ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋˜ input ํƒœ๊ทธ ์†์„ฑ์— ์šฐ๋ฆฌ๊ฐ€ ์ปค์Šคํ…€ ํ›…์—์„œ ๋ฐ˜ํ™˜ํ•œ ํ•จ์ˆ˜๋“ค์„ ์ถ”์ถœํ•˜๋ฉด์„œ ์ด๋ฆ„์œผ๋กœ ์ง€์ •ํ•ด์ฃผ์—ˆ๋˜ nameChangeHandler์™€ nameBlurHandler ํ•จ์ˆ˜๋ฅผ ๊ฐ๊ฐ์˜ ์ด๋ฒคํŠธ ๊ฐ’์œผ๋กœ ํ• ๋‹นํ•˜๊ณ , value๋„ enteredName์œผ๋กœ ํ• ๋‹นํ•œ๋‹ค.
// {
//   nameInputIsInvalid && <p className="error-text">Name must not be empty.</p>;
// }

{
  nameInputHasError && <p className="error-text">Name must not be empty.</p>;
}
  • ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์˜ ๊ฒฝ๊ณ  ๋ฉ”์„ธ์ง€๋ฅผ ๋„์šฐ๋Š” ๋กœ์ง์— ์‚ฌ์šฉํ•˜๋˜ nameInputIsInvalid ๋Œ€์‹  ์šฐ๋ฆฌ๊ฐ€ ์ปค์Šคํ…€ ํ›…์—์„œ ์ถ”์ถœํ•œ nameInputHasError๋ฅผ ๋Œ€์‹  ๋„ฃ์–ด์ค€๋‹ค.
const formSubmitssionHandler = (event) => {
  event.preventDefault();
  // setEnteredNameTouched(true);
  // setEnteredEmailTouched(true);

  if (!enteredNameIsValid || !enteredEmailIsValid) {
    return;
  }

  setEnteredName("");
  setEnteredNameTouched(false);

  setEnteredEmail("");
  setEnteredEmailTouched(false);
};
  • ํผ์„ ์ œ์ถœํ•˜๋Š” ํ•จ์ˆ˜์ธ formSubmitssionHandler ๋˜ํ•œ ์ˆ˜์ •์ด ํ•„์š”ํ•˜๋‹ค. ํ•ด๋‹น ํ•จ์ˆ˜์—์„œ๋Š” ์ƒํƒœ(state)๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๊ณ , ํผ์„ ์ดˆ๊ธฐํ™” ํ•ด์ฃผ๊ณ  ์žˆ๋‹ค. ๋จผ์ €, ์ž…๋ ฅ ๊ฐ’์ด ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด ์ œ์ถœ ์กฐ์ฐจ ๋˜์ง€ ์•Š๋„๋ก ์šฐ๋ฆฌ๊ฐ€ ์„ค์ •ํ•ด์ฃผ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— setEnteredNameTouched(true)๋‚˜ setEnteredEmailTouched(true)์ฒ˜๋Ÿผ input ์ฐฝ์„ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฑด๋“œ๋ ธ๋Š”์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€๋ฅผ ์ฒดํฌํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ์— ์‚ญ์ œํ•ด์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์˜ ํผ์„ ์ดˆ๊ธฐํ™”ํ•ด์ฃผ๋Š” ๋กœ์ง ์—ญ์‹œ ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์ด ๋ถ€๋ถ„๋„ ์ปค์Šคํ…€ ํ›…์— ์•„์›ƒ์†Œ์‹ฑ ํ•ด์ฃผ๋Š” ๊ฒŒ ์ข‹๊ฒ ๋‹ค.
const useInput = (validateValue) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  ...
  const reset = () => {
    setEnteredValue("");
    setIsTouched(false);
  };

  return {
  value: enteredValue,
  isValid: valueIsValid,
  hasError: hasError,
  valueChangeHandler: valueChangeHandler,
  inputBlurHandler: inputBlurHandler,
  reset: reset,
  };
};
  • useInput ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋Œ์•„์™€, ์„ธ ๋ฒˆ์งธ ํ•จ์ˆ˜ reset()์„ ์ถ”๊ฐ€ํ•˜๊ณ  enteredValue์™€ isTouched๋ฅผ ์ดˆ๊ธฐํ™” ํ•ด์ค€ ๋’ค, ํ•ด๋‹น ํ•จ์ˆ˜๋„ ๋™์ผํ•œ ์ด๋ฆ„์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.
const SimpleInput = (props) => {
  const {
    value: enteredName,
    isValid: enteredNameIsValid,
    hasError: nameInputHasError,
    valueChangeHandler: nameChangeHandler,
    inputBlurHandler: nameBlurHandler,
    // โšก๏ธ
    reset: resetNameInput,
    // โšก๏ธ
  } = useInput((value) => value.trim() !== "");
  ...


    const formSubmitssionHandler = (event) => {
    event.preventDefault();
    if (!enteredNameIsValid || !enteredEmailIsValid) {
      return;
    }
    // โšก๏ธ
    resetNameInput();
    // โšก๏ธ
    setEnteredEmail("");
    setEnteredEmailTouched(false);
  };
};
  • SimpleInput ์ปดํฌ๋„ŒํŠธ์—์„œ ์—ญ์‹œ๋‚˜ ์ด์ „๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์—์„œ ๋ฐ˜ํ™˜ํ•œ reset ํ•จ์ˆ˜๋ฅผ resetNameInput๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ํ• ๋‹นํ•˜๊ณ  formSubmitssionHandler ํผ ์ œ์ถœ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ resetNameInput()๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ดˆ๊ธฐํ™”ํ•ด์ค„ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
const nameInputClasses = nameInputHasError // true ์ด๋ฉด,
  ? "form-control invalid" // ๊ฒฝ๊ณ  css
  : "form-control";
  • ๋งˆ์ง€๋ง‰์œผ๋กœ, input ์ฐฝ์˜ css ํด๋ž˜์Šค๋ฅผ ์ •ํ•˜๋Š” ๊ณณ์—์„œ nameInputIsInvalid ๋Œ€์‹  ์ปค์Šคํ…€ ํ›…์—์„œ ๊ฐ€์ ธ์˜จ nameInputHasError๋ฅผ ๋Œ€์ฒดํ•˜์—ฌ ์ˆ˜์ •ํ•ด์ค€๋‹ค.

  • ์ €์žฅํ•˜๊ณ  ์ƒˆ๋กœ๊ณ ์นจํ•˜๋ฉด, name input ์ฐฝ์€ ์ด์ „๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•˜๊ณ , ์ „์ฒด ํผ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์—ญ์‹œ ์ „๊ณผ ๋™์ผํ•œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.


โœฆ ์ถœ์ฒ˜


๐Ÿšจ ํ•ด๋‹น ํฌ์ŠคํŒ…์€ Udemy์˜ โŒœReact ์™„๋ฒฝ ๊ฐ€์ด๋“œโŒŸ ๊ฐ•์˜๋ฅผ ๋ฒ ์ด์Šค๋กœ ํ•œ ๊ธฐ๋ก์ž…๋‹ˆ๋‹ค.
โœ๐Ÿป ๊ฐ•์˜ git repo ๋ฐ”๋กœ๊ฐ€๊ธฐ

profile
์ผ๋‹จ ๊ณต๋ถ€๊ฐ€ '์ ์„ฑ'์— ๋งž๋Š” ๊ฐœ๋ฐœ์ž. ๊ทผ์„ฑ์žˆ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€