Section 11. Form๊ณผ user input

์„œ์ง„ยท2023๋…„ 9์›” 11์ผ
0

React ์™„๋ฒฝ๊ฐ€์ด๋“œ

๋ชฉ๋ก ๋ณด๊ธฐ
11/15
post-thumbnail

๐Ÿ“ Form?

Form์—์„œ๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ๋‹ค์–‘ํ•œ ์ž…๋ ฅ์„ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์–‘ํ•˜๊ณ  ๋งŽ์€ ์–‘์˜ state๋ฅผ ๋‹ค๋ฃจ๊ฒŒ ๋œ๋‹ค. ํ•˜๋‚˜ ์ด์ƒ์˜ ์ž…๋ ฅ ๊ฐ’์ด ๋ชจ๋‘ ์œ ํšจํ•˜๊ฑฐ๋‚˜ ๋ชจ๋‘ ์œ ํšจํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. ๋˜๋Š” ์„œ๋ฒ„๋กœ request๋ฅผ ๋ณด๋‚ธ ํ›„์— ํŠน์ • ๊ฐ’์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธํ•˜๋Š” ๋น„๋™๊ธฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ „์†ก ๋‹น์‹œ ์ƒํƒœ๋ฅผ ์•Œ ์ˆ˜ ์—†์„ ์ˆ˜๋„ ์žˆ๋‹ค.

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

์‚ฌ์šฉ์ž์—๊ฒŒ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋„์šฐ๋Š” ์ˆœ๊ฐ„์€ ํฌ๊ฒŒ 3๊ฐ€์ง€๋กœ ๋‚˜๋ˆ„์–ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

  1. ์‚ฌ์šฉ์ž๊ฐ€ form submit ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ์ˆœ๊ฐ„
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” form์˜ input์—์„œ focus๊ฐ€ ์ œ์™ธ๋œ ์ˆœ๊ฐ„
  3. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ์ˆœ๊ฐ„์ˆœ๊ฐ„ (every keystroke)

1. ์‚ฌ์šฉ์ž๊ฐ€ form submit ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ์ˆœ๊ฐ„

๐Ÿ“ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๋ฐ›์•„์˜ค๊ธฐ

1. ์‚ฌ์šฉ์ž๊ฐ€ input์— ๊ฐ’์„ ์ž…๋ ฅํ•  ๋•Œ ๋งˆ๋‹ค state์— ์ €์žฅํ•˜๊ธฐ

  • ์ฆ‰๊ฐ์ ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์ด ํ‚ค ์ž…๋ ฅ๋งˆ๋‹ค ํ•„์š”ํ•  ๊ฒฝ์šฐ
  • ์ž…๋ ฅ๋œ ๊ฐ’์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ
    (ref๋กœ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, DOM์„ ์ง์ ‘์ ์œผ๋กœ ์กฐ์ž‘ํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ์ง€์–‘ํ•ด์•ผํ•œ๋‹ค)
import {useRef, useState} from 'react';

const SimpleInput = (props) => {
  const [enteredName, setEnteredName] = useState('');

  // input์˜ ๊ฐ’ ๋ฐ›์•„์˜ค๊ธฐ
  const nameInputChangeHandler = event => {
    setEnteredName(event.target.value);
  };

  // form submitํ•  ๋•Œ ๋กœ์ง
  const formSubmissionHandler = event => {
    event.preventDefault(); 
    
    // ์ž…๋ ฅ๊ฐ’ ์ดˆ๊ธฐํ™”
	setEnteredName('');
  };

  return (
    <form onSubmit={formSubmissionHandler}>
        <input 
          type='text' 
          id='name' 
          onChange={nameInputChangeHandler}
          value= {enteredName}
         />
    </form>
  );
};

export default SimpleInput;

2. ref๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด ๋๋‚ฌ์„ ๋•Œ ์ž…๋ ฅ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ

  • ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์ด ํผ์ด ์ œ์ถœ๋˜์—ˆ์„ ๋•Œ ํ•œ ๋ฒˆ๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ
import {useRef, useState} from 'react';

const SimpleInput = (props) => {
  const [enteredName, setEnteredName] = useState('');
  const nameInputRef = useRef();

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

  const formSubmissionHandler = event => {
    event.preventDefault(); 

    // submit ๋ฒ„ํŠผ์ด ๋ˆŒ๋ ธ์„ ๋•Œ ์ž…๋ ฅ๋œ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค
    const enteredValue = nameInputRef.current.value;
  };

  return (
    <form onSubmit={formSubmissionHandler}>
      // ref ์—ฐ๊ฒฐํ•˜๊ธฐ
        <input ref={nameInputRef} type='text' id='name' onChange={nameInputChangeHandler}/>
    </form>
  );
};

export default SimpleInput;

๐Ÿ“ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ถ”๊ฐ€ํ•˜๊ธฐ - ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’์ธ๊ฐ€?

import { useState } from 'react';

const SimpleInput = (props) => {
  const [enteredName, setEnteredName] = useState('');
  // input ์œ ํšจ์„ฑ ๊ฒ€์ฆ state
  const [enteredNameIsValid, setEnteredNameIsValid] = useState(true);

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

  const formSubmissionHandler = (event) => {
    event.preventDefault();

    // input์— ์•„๋ฌด๋Ÿฐ ๊ฐ’์ด ์ž…๋ ฅ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
    if (enteredName.trim() === '') {
      setEnteredNameIsValid(false);
      return;
    }

    setEnteredNameIsValid(true);
    setEnteredName('');
  };

  // invalidํ•œ ๊ฐ’์ผ ๊ฒฝ์šฐ css class ๋™์  ์ ์šฉ
  const nameInputClasses = enteredNameIsValid ? 'form-control' : 'form-control invalid'

  return (
    <form onSubmit={formSubmissionHandler}>
      <div className={nameInputClasses}>
        <label htmlFor="name">Y our Name</label>
        <input 
          type="text" 
          id="name" 
          onChange={nameInputChangeHandler} 
          value={enteredName}
        />
        {/* ์œ ํšจ์„ฑ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋„์šฐ๊ธฐ */}
        {!enteredNameIsValid && <p className='error-text'>Name must not be empty</p>}
      </div>
      <div className="form-actions">
        <button>Submit</button>
      </div>
    </form>
  );
};

export default SimpleInput;

์ด ๋ฐฉ์‹์˜ ๋ฌธ์ œ์ ์€ ์ฒ˜์Œ๋ถ€ํ„ฐ ์—๋Ÿฌ๋ฅผ ๋„์šฐ์ง€ ์•Š๊ธฐ ์œ„ํ•ด์„œ ์ผ๋‹จ input์— ์ž…๋ ฅ๋œ ๊ฐ’์ด valid๋ผ๊ณ  ์„ค์ •ํ•˜๊ณ  ์‹œ์ž‘ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์ผ์ข…์˜ ์†์ž„์ˆ˜์ด์ž ํŽธ๋ฒ•์ธ ๋ฐฉ๋ฒ•์ด๋ผ ์ง€๊ธˆ์€ ๋ฌธ์ œ ์—†์ด ์ž‘๋™ํ•˜์ง€๋งŒ ๋งŒ์•ฝ useEffect()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ž…๋ ฅ๊ฐ’์ด ์œ ํšจํ•  ๊ฒฝ์šฐ httpRequest๋ฅผ ๋ณด๋‚ธ๋‹ค๋ฉด useEffect()์˜ ํŠน์„ฑ์ƒ dependencies์— ์•„๋ฌด๊ฒƒ๋„ ์—†๋‹ค๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋  ๋•Œ ๋ฌด์กฐ๊ฑด ์‹คํ–‰๋˜๋ฏ€๋กœ, valid์˜ ์ดˆ๊ธฐ๊ฐ’์„ true๋กœ ์„ค์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„์ง ์‚ฌ์šฉ์ž๊ฐ€ input์— ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅ ์•ˆํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  http request๋ฅผ ๋ณด๋‚ด๋ฒ„๋ฆฌ๋Š” ์ผ์ด ๋ฐœ์ƒํ•œ๋‹ค.

๋”ฐ๋ผ์„œ input์ด touch ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” state๋ฅผ ํ•˜๋‚˜ ๋” ์„ ์–ธํ•ด์„œ valid state์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ์ข‹์€ ์ฝ”๋“œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค!

๐Ÿ“ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ถ”๊ฐ€ํ•˜๊ธฐ - input์ด ํ„ฐ์น˜ ๋˜์—ˆ๋Š”๊ฐ€?

import { useState } from 'react';

const SimpleInput = (props) => {
  const [enteredName, setEnteredName] = useState('');
  // input ์œ ํšจ์„ฑ ๊ฒ€์ฆ state
  const [enteredNameIsValid, setEnteredNameIsValid] = useState(false);
  // input์— ๊ฐ’์„ ์ž…๋ ฅํ–ˆ๋Š”์ง€ ํ™•์ธ
  const [enteredNameTouched, setEnteredNameTouched] = useState(false);

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

  const formSubmissionHandler = (event) => {
    event.preventDefault();

    // form์„ submitํ•œ ๊ฒฝ์šฐ๋Š” input์„ ๋‹ค touchํ•œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ
    setEnteredNameTouched(true);
    // input์— ์•„๋ฌด๋Ÿฐ ๊ฐ’์ด ์ž…๋ ฅ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
    if (enteredName.trim() === '') {
      setEnteredNameIsValid(false);
      return;
    }

    setEnteredNameIsValid(true);
    setEnteredName('');
  };

  // invalidํ•œ ๊ฒฝ์šฐ = ์ž…๋ ฅ๊ฐ’์ด ์œ ํšจํ•˜์ง€ ์•Š์Œ + ์ž…๋ ฅ์ฐฝ์„ touchํ•œ ๊ฒฝ์šฐ
  const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;

  // invalidํ•œ ๊ฐ’์ผ ๊ฒฝ์šฐ css class ๋™์  ์ ์šฉ
  const nameInputClasses = nameInputIsInvalid ? 'form-control invalid' : 'form-control';

  return (
    <form onSubmit={formSubmissionHandler}>
      <div className={nameInputClasses}>
        <label htmlFor="name">Y our Name</label>
        <input type="text" id="name" onChange={nameInputChangeHandler} value={enteredName} />
        {/* ์œ ํšจ์„ฑ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋„์šฐ๊ธฐ */}
        {nameInputIsInvalid && <p className="error-text">Name must not be empty</p>}
      </div>
      <div className="form-actions">
        <button>Submit</button>
      </div>
    </form>
  );
};

export default SimpleInput;

์ฝ”๋“œ๊ฐ€ ๋” ๊ธธ์–ด์ง€๊ธด ํ–ˆ์ง€๋งŒ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค!


2. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” form์˜ input์—์„œ focus๊ฐ€ ์ œ์™ธ๋œ ์ˆœ๊ฐ„

1๋ฒˆ์˜ ๋ฐฉ๋ฒ•์—์„œ๋Š” submit ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ submithandler๊ฐ€ ๋™์ž‘ํ•  ๊ฒฝ์šฐ์—๋งŒ ์ž…๋ ฅ๊ฐ’์ด ์œ ํšจํ•œ์ง€ ์•„๋‹Œ์ง€์— ๋”ฐ๋ฅธ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ์œ ์ €์—๊ฒŒ ๋ณด์—ฌ์ค€๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ์•ฝ ์ž…๋ ฅ ํ›„ ๋‹ค ์ง€์šฐ๊ณ  input ์นธ์„ ๋น„์›Œ๋’€์„ ๊ฒฝ์šฐ submit ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ธฐ ์ „๊นŒ์ง€๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ์ถœ๋ ฅ๋˜์ง€ ์•Š๋Š”๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒฝํ—˜ํ•˜๊ธฐ์— ์ตœ์„ ์˜ ๊ฒฝํ—˜์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์—†๋‹ค.
๋”ฐ๋ผ์„œ onblur๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ input์—์„œ focus๊ฐ€ ์‚ฌ๋ผ์กŒ์„ ๊ฒฝ์šฐ ์œ ํšจ์„ฑ์„ ๋”ฐ์ ธ์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋„์›Œ์ฃผ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ณ€๊ฒฝํ•ด๋ณด์ž.

๊ธฐ์กด์— input์„ touchํ–ˆ๋Š”์ง€ ์•ˆ ํ–ˆ๋Š”์ง€๋ฅผ ๋”ฐ์ง€๋Š” ๋กœ์ง์„ nameInputBlurHandler์— ์ถ”๊ฐ€ํ•œ๋‹ค.

const SimpleInput = (props) => {
  ...
  // focus out ๋์„ ๋•Œ input์˜ ๊ฐ’ ํ™•์ธ
  const nameInputBlurHandler = (event) => {
    setEnteredNameTouched(true);
    if (enteredName.trim() === '') {
      setEnteredNameIsValid(false);
      return;
    }
  };
  
  return (
  	<form>
      ...
      <input 
        type="text" 
        id="name" 
        onChange={nameInputChangeHandler} 
        // focus out ๋์„ ๋•Œ!
        onBlur={nameInputBlurHandler}
        value={enteredName} 
       />
    </form>
  )
}

์ด ๊ฒฝ์šฐ์˜ ๋ฌธ์ œ์ ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๊ณ  ๋‹ค์‹œ input์— ๊ฐ’์„ ์ž…๋ ฅํ•˜์—ฌ validํ•œ ๊ฐ’์ด ๋˜์–ด๋„ submit๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ธฐ ์ „๊นŒ์ง€๋Š” ๋„์›Œ๋‘” ์—๋Ÿฌ๋ฉ”์‹œ์ง€๊ฐ€ ์—†์–ด์ง€์ง€ ์•Š๋Š” ๊ตฌ์กฐ์ด๋‹ค.

๋”ฐ๋ผ์„œ ๋งˆ์ง€๋ง‰ ์„ธ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์ธ ๋ชจ๋“  keystroke์— ๋”ฐ๋ผ ์œ ํšจ์„ฑ์„ ํŒ๋‹จํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ž์–ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ๊ณ ์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ ๋‹จ์ง€ keystroke์—๋งŒ ์˜์กดํ•˜์—ฌ ์œ ํšจ์„ฑ์„ ํŒ๋‹จํ•œ๋‹ค๋ฉด ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ž…๋ ฅ์— ๋”ฐ๋ผ ๊ฒ€์ฆ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๊ธฐ ๋•Œ๋ฌธ์— ์ข‹์ง€ ๋ชปํ•œ ๋กœ์ง์ด๋ผ๊ณ  ํ•  ์ˆ˜์žˆ๋‹ค. ํ•˜์ง€๋งŒ, 2๋ฒˆ๊ณผ ๊ฒฐํ•ฉํ•ด์„œ focus๋ฅผ ์žƒ๊ณ , ๋‹ค์‹œ focus๋ฅผ ๋ฐ›์€ ํ›„์˜ ๋ชจ๋“  keystroke์„ ๋”ฐ์ง„๋‹ค๋ฉด ๋ณด๋‹ค ํšจ์œจ์ ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์ด ๋  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿ“ 3. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ์ˆœ๊ฐ„์ˆœ๊ฐ„ (every keystroke)

์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๋Š” ๋ถ€๋ถ„์€ ๋ชจ๋‘ ์ฝ”๋“œ๊ฐ€ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  handler๋ฅผ ์ƒ๋Œ€๋กœ ๋™์ผํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ฝ”๋“œ๋ฅผ ๋„ฃ๋Š” ๊ฒƒ ๋ณด๋‹ค, ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋ถ€๋ถ„์„ ํ•˜๋‚˜์˜ ๋ณ€์ˆ˜์— ์ €์žฅํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ๋ณด๊ธฐ ์ข‹์€ ์ฝ”๋“œ์ด๋‹ค.

const SimpleInput = (props) => {
  const [enteredName, setEnteredName] = useState('');
  const [enteredNameTouched, setEnteredNameTouched] = useState(false);

  // ์œ ํšจ์„ฑ ๊ฒ€์ฆ๋ถ€๋ถ„์„ ๋”ฐ๋กœ ๋ณ€์ˆ˜์— ์ €์žฅ
  const enteredNameIsValid = enteredName.trim() !== '';
  const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;

  // keystroke ๋งˆ๋‹ค
  const nameInputChangeHandler = (event) => {
    setEnteredName(event.target.value);
  };

  // focus๋ฅผ ์žƒ์„ ๋•Œ
  const nameInputBlurHandler = (event) => {
    setEnteredNameTouched(true);
  };

  // submit ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ
  const formSubmissionHandler = (event) => {
    event.preventDefault();

    setEnteredNameTouched(true);
    // input์— ์•„๋ฌด๋Ÿฐ ๊ฐ’์ด ์ž…๋ ฅ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
    if (!enteredNameIsValid) {
      return;
    }

    setEnteredName('');
    setEnteredNameTouched(false);
  };
  
  return (...)
}

๐Ÿ“ ์ „์ฒด ์–‘์‹์˜ ์œ ํšจ์„ฑ ํ™•์ธ

ํ˜„์žฌ๋Š” ํ•˜๋‚˜์˜ input์—์„œ ๋ฐ›์•„์˜ค๋Š” ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ๋งŒ์„ ํ™•์ธํ•˜์ง€๋งŒ, ๋ณดํ†ต์˜ ๊ฒฝ์šฐ์—๋Š” input์ด ์—ฌ๋Ÿฌ๊ฐœ ์‚ฌ์šฉ๋˜๊ณ , ํ•ด๋‹น form๋“ค์ด ๋ชจ๋‘ ์œ ํšจํ•ด์•ผํ•œ๋‹ค. ์ „์ฒด ์–‘์‹์ด ์œ ํšจํ•œ์ง€ ์•Œ์•„๋ณด๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

  1. ์ „์ฒด form์ด ์œ ํšจํ•œ์ง€ ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๋Š” state ์ถ”๊ฐ€
  2. ๊ฐ form์˜ ์œ ํšจ์„ฑ ์—ฌ๋ถ€๋ฅผ dependency๋กœ ์ถ”๊ฐ€ํ•œ useEffectํ™œ์šฉ
const SimpleInput = (props) => {
  const [enteredName, setEnteredName] = useState('');
  const [enteredNameTouched, setEnteredNameTouched] = useState(false);
  // ์ „์ฒด form์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  const [formIsValid, setFormIsValid] = useState(false);

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

  // component๊ฐ€ ์ฒ˜์Œ ์‹คํ–‰๋  ๋•Œ, dependency์˜ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋จ
  // ๋ชจ๋“  form์ด validํ•œ์ง€ ํ™•์ธํ•˜๋ คํ•จ!
  useEffect(()=> {
    if (enteredNameIsValid) {
      setFormIsValid(true);
    } else {
      setFormIsValid(false);
    }
  }, [enteredNameIsValid]);
  
  return (...)
}

ํ•˜์ง€๋งŒ ์‚ฌ์‹ค useEffect ๋‚ด๋ถ€์—์„œ side effect๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ฏ€๋กœ useEffect๋ฅผ ์‚ฌ์šฉํ•  ์ด์œ ๊ฐ€ ์—†๋‹ค. ๋”ฐ๋ผ์„œ ๋‹จ์ˆœํ•˜๊ฒŒ ์ „์ฒด form์ด validํ•œ์ง€๋ฅผ boolean๊ฐ’์œผ๋กœ ์ €์žฅํ•˜๋Š” ๋ณ€์ˆ˜๋ฅผ ๋‘๊ณ , ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  form์˜ valid์—ฌ๋ถ€๋ฅผ if๋ฌธ์œผ๋กœ ๋‘์–ด์„œ ์ „์ฒด form์˜ ์œ ํšจ์„ฑ์—ฌ๋ถ€๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ๋œ๋‹ค!

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

  // ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” form๋“ค์˜ ์œ ํšจ์„ฑ์—ฌ๋ถ€ ํ™•์ธํ•˜๋Š” ๋ถ€๋ถ„
  const enteredNameIsValid = enteredName.trim() !== '';
  const enteredAgeIsValid = enteredName.trim() !== '';
  const enteredEmailIsValid = enteredName.trim() !== '';

  const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;

  // ์ „์ฒด form์˜ valid ์—ฌ๋ถ€ ํ™•์ธ ๋ณ€์ˆ˜
  let formIsValid = false;
  if (enteredNameIsValid && enteredAgeIsValid && enteredEmailIsValid) {
    formIsValid = true;
  } 

์ค‘๋ณต๋œ ๋ถ€๋ถ„ ์ œ๊ฑฐํ•˜๊ธฐ! - custom Hook

์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๋Š” ๋ถ€๋ถ„์ด input ํƒœ๊ทธ๋งˆ๋‹ค ๋กœ์ง์ด ๊ฐ™์œผ๋‹ˆ ์ด ๋ถ€๋ถ„์„ ๊ณ„์†ํ•ด์„œ ์ž‘์„ฑํ•˜๊ธฐ๋ณด๋‹ค๋Š” custom Hook์œผ๋กœ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

useInput custom Hook ๋งŒ๋“ค๊ธฐ

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 valueChangedHandler = (event) => {
    setEnteredValue(event.target.value);
  };
  const inputBlurHandler = (event) => {
    setIsTouched(true);
  };

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

  return {
    value: enteredValue,
    isValid: valueIsValid,
    hasError,
    valueChangedHandler,
    inputBlurHandler,
    reset
  };
};

export default useInput;

์‚ฌ์šฉํ•˜๊ธฐ

import useInput from '../hooks/use-input';

// alias ์ด์šฉํ•ด์„œ return ๊ฐ’๋“ค์„ name๊ณผ email์— ๊ฐ๊ฐ ์‚ฌ์šฉํ•˜๊ธฐ!
const SimpleInput = (props) => {
  const {
    value: enteredName,
    isValid: enteredNameIsValid,
    hasError: nameInputHasError,
    valueChangedHandler: nameChangedHandler,
    inputBlurHandler: nameBlurHandler,
    reset: resetNameInput,
  } = useInput((value) => value.trim() !== '');

  const {
    value: enteredEmail,
    isValid: enteredEmailIsvalid,
    hasError: emailInputHasError,
    valueChangedHandler: emailChangeHandler,
    inputBlurHandler: emailBlurHandler,
    reset: resetEmailInput,
  } = useInput((value) => value.includes('@') && value.includes('.'));

  let formIsValid = false;
  if (enteredNameIsValid && enteredEmailIsvalid) {
    formIsValid = true;
  }

  // form submit!
  const formSubmissionHandler = (event) => {
    event.preventDefault();

    // input์— ์•„๋ฌด๋Ÿฐ ๊ฐ’์ด ์ž…๋ ฅ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
    if (!enteredNameIsValid || !enteredEmailIsvalid) {
      return;
    }

    resetNameInput();
    resetEmailInput();
  };

  // invalidํ•œ ๊ฐ’์ผ ๊ฒฝ์šฐ css class ๋™์  ์ ์šฉ
  const nameInputClasses = nameInputHasError ? 'form-control invalid' : 'form-control';
  const emailInputClasses = emailInputHasError ? 'form-control invalid' : 'form-control';

  return (...์ƒ๋žต);
};

export default SimpleInput;

formIk ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

FORMIK ๊ณต์‹๋ฌธ์„œ

profile
๐Ÿซง โ˜๏ธ ๐ŸŒ™ ๐Ÿ‘ฉ๐Ÿปโ€ข๐Ÿ’ป ๐ŸŒฟ ๐Ÿฑ ๐Ÿ–ฑ ๐ŸŸ ๐Ÿš€ โญ๏ธ ๐Ÿงธ ๐Ÿ€ ๐Ÿ’—

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