🌟 폼(form) 태그

🔖 폼(form) 태그

  • HTML 폼 엘리먼트는 폼 엘리먼트 자체가 내부 상태를 가짐
  • React의 다른 DOM 엘리먼트와 다르게 동작

→ form 은 제어 컴포넌트(controlled components) 중 하나

→ 리액트 form에서는 컴포넌트 안 state 데이터가 사용자가 화면에 보고 있는 데이터와 동일하도록 만드는 것을 권장

→ submit 같은 특정 이벤트가 발생했을 때 입력받은 데이터에 접근하는 방식

→ 리액트의 form 은 입력할 때 마다 특정 부분이 리렌더링 되어도 리액트 자체적으로 효율적이게 DOM을 없데이트 해주기 때문에 성능은 크게 걱정하지 않아도 됨

  • form 안에 여러가지 요소가 들어있을 땐 name 속성을 부여해 각각을 구분할 수 있도록 함

event.target.name 으로 조회 가능

  • value 속성을 통해 값을 부여한 경우, 타이핑을 해도 업데이트 되지 않음

→ onChange, onSubmit을 통해 업데이트 함수를 지정

 

🌟 form 태그의 이벤트

🔖 onChange 이벤트

  • input , select , textarea의 요소에서 onchange 이벤트 발생

→ input , select , textarea에서 onChange 이벤트는 요소 값 각각 모든 변경마다 발생

 

🔖 onSubmit 이벤트

  • form 태그는 onSubmit 이벤트 발생

→ onSubmit 이벤트를 통해 submit 이벤트가 실행 되었을 때 발생

→ onChange 이벤트와 달리 요소 값 각각의 모든 변경마다 반드시 발생하지는 않음

  • onChange와 onSubmit에서의 event.preventDefault는 페이지가 리로딩되는 액션을 못하게 함

 

🌟 제어 컴포넌트

🔖 제어 컴포넌트 (Controlled Component)

  • HTML에서 <input>, <textarea>, <select>와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트
  • React에서는 변경할 수 있는 state가 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트
  • React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합
  • 폼을 렌더링하는 React 컴포넌트폼에 발생하는 사용자 입력값을 제어
  • React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트“라고 함
const [상태값, 상태변경함수] = useState("")

render() {
    return (
            <form>
                <input type="text" value={**상태값**} onChange={handleChange} />
            </form>
)}
  • value 어트리뷰트는 폼 엘리먼트에 설정되므로 표시되는 값은 state
  • React state는 신뢰 가능한 단일 출처 (single source of truth)가 됨
  • React state를 업데이트하기 위해 모든 키 입력에서 handleChange 가 동작하기 때문에 사용자가 입력할 때 보여지는 값이 업데이트
  • 제어 컴포넌트로 사용하면, input의 값은 항상 React state에 의해 결정

 

🌟 input 태그, Form 태그 및 이벤트 - 예시1

🔖 onChange 이벤트

  • input 태그의 onChange가 이벤트가 발생한 것을 감지하면 handleChange() 함수 실행
  • handleChange() 함수는 input 값에 입력된 value 값을 출력
  • input 태그 안에 있는 값은 HTML에서 관리하고 있다(리액트에서 관리 x)
/*SimpleForm.js*/
import React from "react"

export default function SimpleForm() {
    const handleChange = (e) => {
        console.log(e.target.value)
    }

    return (
        <>
            <label>닉네임 : </label>
            <input type="text" name="nickname" onChange={handleChange}></input>
        </>
    )
}

 

🔖 onSubmit 이벤트

  • form 태그를 생성해주고 form 태그에도 onSubmit 이벤트를 추가
  • submit 이벤트가 발생하면 handleSunmit() 함수가 실행
  • form에서 submit 된 데이터는 HTML의 submit에서 관리

→ 리액트에서 form 태그의 입력 값을 사용하기 위해서는 state가 필요

/*SimpleForm.js*/
import React from "react"

export default function SimpleForm() {
    const handleChange = (e) => {
        console.log(e.target.value)
    }

    const handleSubmit = (e) => {
        console.log(e)
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>닉네임 : </label>
            <input type="text" name="nickname" onChange={handleChange}></input>
            <input type="submit" value="제출"></input>
        </form>
    )
}

 

🔖 제어 컴포넌트 전환

  • 제어 컴포넌트로 사용하기 위해 input과 연결 시켜줄 React state에 선언(nickname)
  • value={}로 input 엘리먼트와 React state 연결
  • 리액트의 state로 input 태그의 값을 제어할 수 있음
/*SimpleForm.js*/
import React, { useState } from "react"

export default function SimpleForm() {
    const [nickname, setNickname] = useState("")

    const handleChange = (e) => {
        setNickname(e.target.value)         //입력값 넣기
    }

    const handleSubmit = (e) => {
        e.preventDefault()                  //form의 새로고침 막기
        alert(nickname)
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>닉네임 : </label>
            <input type="text" name="nickname" onChange={handleChange} **value={nickname}**></input>
            <input type="submit" value="제출"></input>
        </form>
    )
}

 

🌟 input 태그, Form 태그 및 이벤트 - 예시2

🔖 onChange 이벤트

  • input 태그의 onChange가 이벤트가 발생한 것을 감지하면 handleChange() 함수 실행
  • handleChange() 함수는 input 값에 입력된 value 값을 출력
  • input 태그는 각 info.name과 info.phone 값을 갖는 제어 컴포넌트
import React, { useState } from 'react';

export default function Form(props) {

    const [info, setInfo] = useState({
        name: 'merry',
        phone: '010-6666-3333'
    })

    const **handleChange** = (e) => {
        e.preventDefault();
        setInfo(info => {
            const update = {
                ...info,
                [e.target.name]: e.target.value
            }
            return update
        })
        console.log(info)               //업데이트 된 info 정보 출력
    }

    return (
        <form>
            <input
                type="text"
                name="name"
                **value={info.name}**
                **onChange={handleChange}**
            />
            <input
                type="text"
                name="phone"
                **value={info.phone}**
                **onChange={handleChange}**
            />
        </form>
    );
}
  • 초기 state에는 name : merry, phone: 010-6666-3333이 들어가 들어있는 제어 컴포넌트
  • input 값의 초기 값은 merry와 010-6666-3333

  • input의 value를 변경할 때마다 onChange 이벤트가 발생하여 state 값을 출력해 줌

 

🔖 onSubmit 이벤트

  • button을 클릭 했을 때, submit 이벤트가 발생
  • form에 연결되어 있는 onSubmit 이벤트 핸들러 동작
  • state 값을 출력해 줌
import React, { useState } from 'react';

export default function Form(props) {

    const [info, setInfo] = useState({
        name: 'merry',
        phone: '010-6666-3333'
    })

    const onChange = (e) => {
        e.preventDefault();
        setInfo(info => {
            const update = {
                ...info,
                [e.target.name]: e.target.value
            }
            return update
        })
        console.log(info)               //업데이트 된 info 정보 출력
    }

    const handleSubmit = (e) => {
        e.preventDefault()
        alert(`${info.name} ${info.phone}`)
    }

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                name="name"
                value={info.name}
                onChange={onChange}
            />
            <input
                type="text"
                name="phone"
                value={info.phone}
                onChange={onChange}
            />
            <button type="submit">로그인</button>
        </form>

    );
}

 

🌟 textarea 태그

🔖 textarea

  • React에서 <textarea>value 어트리뷰트를 대신 사용
  • <textarea>를 사용하는 폼은 한 줄 입력을 사용하는 폼과 비슷하게 작성
/*SimpleForm.js*/
import React, { useState } from "react"

export default function SimpleForm() {
    const [nickname, setNickname] = useState("")

    const handleChange = (e) => {
        setNickname(e.target.value)         //입력값 넣기
    }

    const handleSubmit = (e) => {
        e.preventDefault()                  //form의 새로고침 막기
        alert(nickname)
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>닉네임 : </label>
            **<textarea value={nickname} onChange={handleChange}></textarea>**
            <input type="submit" value="제출"></input>
        </form>
    )
}

 

🌟 select 태그

🔖 select

  • React에서는 selected 어트리뷰트를 사용하는 대신
  • select태그에 value 어트리뷰트를 사용
/*SimpleForm.js*/
import React, { useState } from "react"

export default function SimpleForm() {
    const [nickname, setNickname] = useState("")

    const handleChange = (e) => {
        setNickname(e.target.value)         //입력값 넣기
    }

    const handleSubmit = (e) => {
        e.preventDefault()                  //form의 새로고침 막기
        alert(nickname)
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>닉네임 : </label>
            <select value={nickname} onChange={handleChange}>
                <option value="coco">coco</option>
                <option value="momo">momo</option>
                <option value="dodo">dodo</option>
                <option value="nono">nono</option>
            </select>
            <input type="submit" value="제출"></input>
        </form>
    )
}

 

🌟 다중 입력 제어

🔖 다중 입력 제어

  • 여러 input 엘리먼트를 제어해야할 때, 각 엘리먼트에 name 어트리뷰트를 추가
  • event.target.값 을 이용해서 핸들러가 어떤 작업을 할 지 선택

🔖 두개의 입력 폼

  • 두개의 입력 값 받기
  • 두개의 상태 필요, 두개의 input 값 필요
  • 입력 값이 두개이므로 e.target.name을 통해서 핸들러가 어떤 작업을 할지 구분
/*SimpleForm.js*/
import React, { useState } from "react"

export default function SimpleForm() {
    const [nickname, setNickname] = useState("")
    const [password, setPassword] = useState("")

    const handleChange = (e) => {
        if (e.target.name === "nickname") return setNickname(e.target.value)
        return setPassword(e.target.value)
    }

    const handleSubmit = (e) => {
        e.preventDefault()                  //form의 새로고침 막기
        alert(`닉네임 : ${nickname}, 패스워드 : ${password}`)
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>닉네임 : </label>
            <input type="text" name="nickname" onChange={handleChange} value={nickname}></input>
            <label>패스워드 : </label>
            <input type="text" name="password" onChange={handleChange} value={password}></input>
            <input type="submit" value="제출"></input>
        </form>
    )
}

 

🔖 두개의 입력 폼 - 객체로 받기

  • state가 각각 있으면 복잡

→ 객체로 저장해보자

  • userInput은 객체 이름

→ nickname, password가 프로퍼티

→ userInput.nickname

→ userInput.password

/*SimpleForm.js*/
import React, { useState } from "react"

export default function SimpleForm() {
    **const [userInputs, setUserInputs] = useState({
        nickname: "",
        password: "",
    })**

    const handleChange = (e) => {
        **setUserInputs({ ...userInputs, [e.target.name]: e.target.value })**
    }

    const handleSubmit = (e) => {
        e.preventDefault()                          //form의 새로고침 막기
        **const { nickname, password } = userInputs**   //구조분해 할당
        alert(`닉네임 : ${nickname}, 패스워드 : ${password}`)
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>닉네임 : </label>
            <input type="text" name="nickname" onChange={handleChange} **value={userInputs.nickname}**></input>
            <label>패스워드 : </label>
            <input type="text" name="password" onChange={handleChange} **value={userInputs.password}**></input>
            <input type="submit" value="제출"></input>
        </form>
    )
}
  • e.target으로 입력 받은 값은, userInputs에 추가하도록 한다.

 

  • e.target 안에는 input으로 입력한 값이 들어있다.

 

  • 객체 상태에 있는 값을 구조분해 할당 하여 submit 이벤트가 발생하면 alert창을 띄워주자.

🌟 비제어 컴포넌트

🔖 비제어 컴포넌트

  • DOM 자체에서 폼 데이터가 다루어짐

※ 제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어짐

  • 비제어 컴포넌트를 만들려면 ref를 사용 하여 DOM에서 폼 값을 가져올 수 있음
  • DOM에 신뢰 가능한 출처를 유지함
  • 비제어 컴포넌트를 사용할 때 React와 non-React 코드를 통합하는 것이 쉬움

 

🌟 ref

🔖 ref

→ 리액트에서 DOM에 직접 접근

  • 클래스 컴포넌트 일 때, ref 사용
import {createRef} from "react"
  • 함수 컴포넌트 일 때, ref를 사용
  • useRef는 Hook 함수
import {useRef} from "react"
  • useRef를 통해서 객체를 만들 수 있음

→ current라는 키가 자동으로 들어있음

→ ref 태그를 태그에 가져다 붙여주면, current는 input 태그를 가지고 있음

 

/*SimpleForm.js*/
import React, { useRef } from "react"

//ref를 활용 -> 비제어 컴포넌트 방식으로 form을 다뤄보자
export default function UnControlledForm() {
    const inputRef = useRef()

    const handleChange = (e) => {
        console.log(e.target.value)
    }

    return (
        <>
            <label>닉네임 : </label>
            <input type="text" name="nickname" onChange={handleChange} ref={inputRef}></input>
        </>
    )
}
  • DOM에 입력한 값을 가져올 수 있음
  • useRef() 선언

→ input 태그에 ref = {inputRef} 추가

→ 리액트와 DOM이 연결되어 입력 값을 가져 올 수 있음

/*SimpleForm.js*/
import React, { useRef } from "react"

//ref를 활용 -> 비제어 컴포넌트 방식으로 form을 다뤄보자
export default function UnControlledForm() {
    const inputRef = useRef()

    const handleSubmit = (e) => {
        e.preventDefault()
        alert(inputRef.current.value)
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>닉네임 : </label>
            <input type="text" name="nickname" ref={inputRef}></input>
            <input type="submit" value="제출"></input>
        </form>
    )
}

 

🌟 form 태그 활용 - ToDo List

🔖 기본 초기화 코드

import React, { useState } from "react";

const ToDo = () => (
    <tr>
        <td>
            <label>123</label>
        </td>
        <td>
            <input />
        </td>
        <td>
            <label>밥먹기</label>
        </td>
    </tr>
)

export default function MyTodoList() {

    const addToStart = () => {

    }

    const addToEnd = () => {

    }

    const sortByEarliest = () => {

    }

    const sortByLatest = () => {

    }

    return (
        <div>
            <code>Todo List</code>
            <br />
            <button onClick={addToStart}>앞에 추가</button>
            <button onClick={addToEnd}>뒤에 추가</button>
            <button onClick={sortByEarliest}>오름차순 정렬</button>
            <button onClick={sortByLatest}>내림차순 정렬</button>
            <table>
                <tr>
                    <th>ID</th>
                    <th />
                    <th>created at</th>
                </tr>
                <ToDo />
            </table>
        </div>
    )
}

 

🔖 state에 있는 값을 테이블 데이터를 만들자

  • 데이터가 저장될 state 생성

→ todoCounter는 id값

→ createAt는 생성 날짜

 

  • state에 있는 값을 하나씩 꺼내서 테이블 데이터로 ToDo리스트 생성

 

  • 시작 값은 1이고, Date로 현재 시간을 받아왔으니까 초기 값은 아래와 같다.

import React, { useState } from "react";

const ToDo = (props) => (
    <tr>
        <td>
            <label>{props.id}</label>
        </td>
        <td>
            <input />
        </td>
        <td>
            <label>{props.createdAt.toTimeString()}</label>
        </td>
    </tr>
);

export default function MyTodoList() {
    const date = new Date()
    const todoCounter = 1
    const [state, setState] = useState({
        todoCounter: todoCounter,
        list: [
            {
                id: todoCounter,           //id는 생성 번호
                createdAt: date,          //생성 날짜 / 시간
            }
        ]
    })

    const addToStart = () => {

    }

    const addToEnd = () => {

    }

    const sortByEarliest = () => {

    }

    const sortByLatest = () => {

    }

    return (
        <div>
            <code>Todo List</code>
            <br />
            <button onClick={addToStart}>앞에 추가</button>
            <button onClick={addToEnd}>뒤에 추가</button>
            <button onClick={sortByEarliest}>오름차순 정렬</button>
            <button onClick={sortByLatest}>내림차순 정렬</button>
            <table>
                <tr>
                    <th>ID</th>
                    <th />
                    <th>created at</th>
                </tr>
                {state.list.map((todo) => (
                    <ToDo key={todo.id} {...todo} />
                ))}
            </table>
        </div>
    )
}

 

🔖 addToStart() 함수를 통해서 뒤쪽에 리스트를 생성하자

  • addToStart() 함수

→ 새로운 Date 객체를 만들어서 현재 날짜와 시간을 생성

→ 기존에 있는 todoCounter에 +1 해서 숫자 증가

→ 생성한 새로운 값을 newList에 저장(…을 이용해서 기존 값을 복사하고, 새롭게 복사된 리스트에 추가하자)

→ setState()를 통해서 state에 저장

  • addToStart() 함수가 동작하면서 뒤쪽으로 순차적으로 ToDo가 생성

🔖 addToEnd() 함수를 통해서 앞쪽에 리스트를 생성하자

  • addToEnd() 함수

→ newList의 순서를 변경하면, 앞쪽으로 생성된다.

 

🔖 sortByEarliest() 함수를 통해서 오름차순 정렬하자

  • sortByEarliest() 함수

→ createAt를 이용해서 정렬

→ 새롭게 정렬된 객체 데이터를 setState를 통해서 저장

  • 오름차순 정렬된 순서로 다시 렌더링

🔖 sortByLatest() 함수를 통해서 내림차순 정렬하자

  • sortByLatest() 함수

→ createAt를 이용해서 정렬

→ 새롭게 정렬된 객체 데이터를 setState를 통해서 저장

 

🔖 전체 코드

import React, { useState } from "react";

const ToDo = (props) => (
    <tr>
        <td>
            <label>{props.id}</label>
        </td>
        <td>
            <input />
        </td>
        <td>
            <label>{props.createdAt.toTimeString()}</label>
        </td>
    </tr>
);

export default function MyTodoList() {
    const date = new Date()
    const todoCounter = 1
    const [state, setState] = useState({
        todoCounter: todoCounter,
        list: [
            {
                id: todoCounter,           //id는 생성 번호
                createdAt: date,          //생성 날짜 / 시간
            }
        ]
    })

    const addToStart = () => {
        const date = new Date()
        const nextId = state.todoCounter + 1
        const newList = [...state.list, { id: nextId, createdAt: date }]
        setState({
            list: newList,
            todoCounter: nextId,
        });
    }

    const addToEnd = () => {
        const date = new Date()
        const nextId = state.todoCounter + 1
        const newList = [{ id: nextId, createdAt: date }, ...state.list]
        setState({
            list: newList,
            todoCounter: nextId,
        });
    }

    const sortByEarliest = () => {
        const sortedList = state.list.sort((a, b) => {
            return a.createdAt - b.createdAt
        })

        setState({
            list: [...sortedList]
        })
    }

    const sortByLatest = () => {
        const sortedList = state.list.sort((a, b) => {
            return b.createdAt - a.createdAt
        })

        setState({
            list: [...sortedList]
        })
    }

    return (
        <div>
            <code>Todo List</code>
            <br />
            <button onClick={addToStart}>앞에 추가</button>
            <button onClick={addToEnd}>뒤에 추가</button>
            <button onClick={sortByEarliest}>오름차순 정렬</button>
            <button onClick={sortByLatest}>내림차순 정렬</button>
            <table>
                <tr>
                    <th>ID</th>
                    <th />
                    <th>created at</th>
                </tr>
                {state.list.map((todo) => (
                    <ToDo key={todo.id} {...todo} />
                ))}
            </table>
        </div>
    )
}

 

 

[React] 폼(form), 제어 컴포넌트, 비제어 컴포넌트