폼 - 주요 개념

hongregii·2023년 3월 1일
0

기존 HTML 에서는...

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>
Name:

그냥 인풋창이다. 문제는 안에 타이핑을 해도 리액트가 그 값을 실시간으로 알지는 못하고,
submit 인풋을 눌렀을 때 자동으로 이벤트가 생기고 새로고침된다는 것이다.

리액트가 지향하는 Client Side Rendering이랑은 방향성이 다르다고 볼 수 있다. 프런트 - 백이 명확히 구분되지 않던 MVC 패턴 방법론에서 사용하던 방식.

제어 컴포넌트 Controlled Component

리액트가 <input />, <textarea / >, <select /> 같은 폼 엘리먼트를 state를 사용해서 관리하게끔 바꿔보자.

Form 도 렌더링한다는 개념으로 접근하면 됨!

function Form() {
  const [firstName, setFirstName] = useState(''); // state 선언
  // ...
  return (
    <input
      value={firstName} // ...value prop에 state 넣음...
      onChange={e => setFirstName(e.target.value)} // ... onChange prop에 setState 넣음!
    />
  );
}

1. state 선언

주의 : 절대 초기값을 null 이나 undefined로 하지 말 것. value prop에 넣을 값인데, 비워놓고 싶으면 '' 사용. null이나 undefined를 넣어주면 사실상 prop을 지정 안한거나 마찬가지임. 그래서 uncontrolled로 인식한다.

2. value prop에 state 넣음

주의 : checkbox 태그, radio 태그 같은 녀석들은 checked / defaultChecked prop 사용해야 함.

3. onChange (or onClick) prop에 setState 넣음!

주의 : onChange / onClick 안에 setState(event.target.value) 함수를 넣지 않으면 아무리 입력해도 value prop에 있는 state 값을 렌더링한다.
즉 화면에 입력이 안됨!

+ <input> 태그마다 name prop 줘야 함. 요놈을 form data 객체의 key값으로, value를 value로 사용함.

++. defaultValue 는 초기값일 뿐. value를 넣지 않으면 uncontrolled component가 됨.

submit

handleSubmit() 같은 함수 만들어서 하는게 좋다.

 function handleSubmit(e) {
    // 새로고침 방지
    e.preventDefault();

    // form 태그 onSubmit prop에 handleSubmit 함수를 넣으면 form 채로 fetch API 보낼 수 있음 (post). php에 쫌 더 어울리긴 함... 옛날방식:
    fetch('/some-api', { method: form.method, body: formData });

    // state에 controlled input value를 가지고 있으니까 걍 이 값을 가지고 JSON으로 axios post 요청 보내는게 더 깔끔할듯:
    const formJson = Object.fromEntries(formData.entries());
    console.log(formJson);
  }

setState는 비동기잖아 !

사실 그래서 발생하는 문제가 크진 않다.
그러나, 이 문제가 항상 눈에 밟히기 마련이다.
바로 console.log()를 찍었을 때 한박자 느린 state값이 찍힌다는 사실.

예를 들어, inputValue라는 객체를 state로 선언하고, input 태그의 name을 key로, value를 value로 추가해나간다고 치자.

// state 선언
const [inputValue, setInputValue] = useState({});

// onChange 에 넣을 함수 선언
const handleInputChange = (e) => {
  setInputValue( {[e.target.name] : e.target.value});
  console.log(inputValue) // 입력마다 console.log 출력할것임
} 

 ... JSX 부분 ...

 <input
	type="email"
    name="email"
    value={inputValue.email}
    onChange={handleInputChange}
  />
      
  <input
	type="number"
    name="contact"
    value={inputValue.contact}
    onChange={handleInputChange}
  />
 
  <input
	type="text"
    name="address"
    value={inputValue.address}
    onChange={handleInputChange}
  />

이렇게 하면 한 handleInputChange 함수로 한번에 controlled form component를 처리 가능하다.

입력할 때마다 {contact : "032323", address:"eead"} 이런 식으로 inputValue state가 바뀔 것이고, console이 찍힐 것이다.

그러나, setState가 비동기이기 때문에 입력하는 값보다 한박자 느리게 console이 찍혀서 짜증난다.

사실 큰 문제는 아닌 것이, 어차피 submit 하는 api를 보낼 때면 잘 바뀌어있을 것이다. setState 이후에 뭔가를 바로 실행시킬 때만 문제가 생김.

해결책

이런 경우, useEffect() 안에 console.log를 넣어주는 것이 가장 좋은 해결책. dependency 값에는 당연히 바뀌는 state를 넣자.

또는, setState()안에 콜백함수로 console.log()를 넣어줄 수도 있다.

const handleInputChange = (event) =>  {
  setInputValue({ [event.target.name]: event.target.value }, () => {
    console.log(inputValue); // 비동기 state update 끝나고 이거 해줘
  });
}

useEffect() 안에만 넣어와서 생각 못했는데, 이렇게 쓰는게 덜 귀찮고 보기도 편할지도!

어차피 콘솔 찍는게 큰 로직 덩어리는 아니기 때문에, 괜히 useEffect를 사용하면 중요한 코드처럼 보여서 힘주고 보게 되니깐...

profile
잡식성 누렁이 개발자

0개의 댓글