개발/리액트

리액트 핵심만 훑어보자 #12 폼

brobro332 2025. 3. 9. 00:18
반응형
📘 『소플의 처음 만난 리액트』를 읽고 정리한 글입니다.

 

  • 폼은 사용자로부터 입력을 받기 위해 사용한다.
  • HTML의 폼은 엘리먼트 내부에 각각의 상태가 존재한다.
  • 반면 리액트의 폼은 컴포넌트 내부에서 상태를 통해 데이터를 관리한다.

 

<form>
    <label>
    	이름 :
        <input type="text" name="name"/>
    </label>
    <button type="submit">제출</button>
</form>
  • 상기 코드는 HTML의 폼이다.
  • 리액트에서도 작동은 하지만 자바스크립트 코드를 통해 사용자가 입력한 값에 접근하기는 불편한 구조이다.
  • 이를 해소하기 위해선 제어 컴포넌트를 숙지해야 한다.

 

제어 컴포넌트

  • 리액트의 통제를 받으며, 사용자 입력에 대한 접근 및 제어를 처리하는 입력 폼 엘리먼트이다.
  • 위의 HTML 폼을 리액트의 제어 컴포넌트로 만드는 것이다.
function NameForm(props) {
    const [value, setValue] = useState('');
    
    const handleChange = (event) => {
    	setValue(event.target.value);
    }
    
    const handleSubmit = (event) => {
    	alert('입력한 이름 : ' + value);
        event.preventDefault();
    }
    
    return (
    	<form onSubmit={handleSubmit}>
        	<label>
            	이름 :
            	<input type="text" value={value} onChange={handleChange}/>
            </label>
            <button type="submit">제출</button>
        </form>
    );
}
  • 상기 코드처럼 제어 컴포넌트를 사용하면 입력 값이 리액트 컴포넌트 상태를 통해 관리된다.
  • 즉 입력값을 원하는 대로 조종할 수 있게 된다.
  • 예를 들어 사용자가 입력한 모든 알파벳을 대문자로 변경시켜 관리하고 싶다면 다음 코드와 같이 작성하면 된다.

 

const handleChange = (event) => {
    setValue(event.target.value.toUpperCase());
}

 

textarea

function RequestForm(props) {
    const [value, setValue] = useState('요청사항을 입력하세요.');
    
    const handleChange = (event) => {
    	setValue(event.target.value);
    }
    
    const handleSubmit = (event) => {
    	alert('입력한 요청사항 : ' + value);
        event.preventDefault();
    }
    
    return (
    	<form onSubmit={handleSubmit}>
        	<label>
            	요청사항 :
                <textarea value={value} onChange={handleChnage}/>
            </label>
                <button type="submit">제출</button>
        </form>
    )
}
  • 상기 코드는 textarea에 입력할 때마다 state의 값이 바뀌고, 바뀔 때마다 textarea의 value의 값이 바뀐 채로 재렌더링된다.
  • 여기에서는 value 선언 시 초깃값을 넣어주었기 때문에 처음 렌더링 될 때부터 textarea에 텍스트가 나타난다.

 

select

function FruitSelect(props) {
    const [value, setValue] = useState('grape');
    
    const handleChange = (event) => {
    	setValue(event.target.value);
    }
    
    const handleSubmit = (even) => {
    	alert('선택한 과일 : ');
        event.preventDefault();
    }
    
    return (
    	<form onSubmit={handleSubmit}>
        	<label>
            	과일을 선택하세요 :
                <select value={value} onChange={handleChange}>
                	<option value="apple">사과</option>
                    <option value="banana">바나나</option>
                    <option value="grape">포도</option>
                    <option value="watermelon">수박</option> 
            </label>
        </form>
    );
}
  • select 태그에서 특정 값을 선택하려면 value 속성을 사용하면 된다.
  • 다중 선택이 가능하도록 하려면 multiple={true}을 select 태그에 속성으로 추가하면 된다.

 

input file

<input type="file"/>
  • 입력값이 읽기 전용이므로 리액트에서는 비제어 컴포넌트가 된다.

 

여러 개의 입력 다루기

  • 하나의 컴포넌트에서 여러 개의 입력을 다루려면 여러 개의 state를 선언하여 각각의 입력에 대해 사용하면 된다.
  • 예제 코드는 생략한다.

 

input null value

  • 제어 컴포넌트에 value 속성을 정해진 값으로 넣으면 코드를 수정하지 않는 한 입력값을 바꿀 수 없다.
  • 만약 value 값은 넣되 자유롭게 입력할 수 있게 만들고 싶다면 값에 undefined 또는 null을 넣어주면 된다.

 

실습

// ...

const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [memberList, setMemberList] = useState([]); 
const [page, setPage] = useState(1);

const pageSize = 10;
const startIndex = (page - 1) * pageSize;
const currentPageData = memberList.slice(startIndex, startIndex + pageSize);

// ...

return (
    <Box>
      <Box display="flex" gap={2} sx={{ marginBottom:"15px" }}>
        <TextField 
          label="이메일" 
          variant="outlined" 
          size="small" 
          sx={{ flex: 1 }}
          value={email}
          onChange={(e) => setEmail(e.target.value)} 
        />
        <TextField 
          label="이름" 
          variant="outlined" 
          size="small" 
          sx={{ flex: 1 }} 
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </Box>
      <Box>
        <TableContainer component={Paper}>
          <Table sx={{ tableLayout: "fixed", width: "100%" }}>
            <TableHead>
              <TableRow>
                <TableCell sx={{ width: "20%" }}>이메일</TableCell>
                <TableCell sx={{ width: "20%" }}>이름</TableCell>
                <TableCell sx={{ width: "30%" }}>소개</TableCell>
                <TableCell sx={{ width: "10%" }}>처리</TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
            {currentPageData.map((member) => (
              <TableRow key={member.id} sx={{ height: "30px" }}>
                <TableCell sx={{ paddingBottom: "5px", paddingTop: "5px" }}>
                  {member.email}
                </TableCell>
                <TableCell sx={{ paddingBottom: "5px", paddingTop: "5px" }}>
                  {member.name}
                </TableCell>
                <TableCell sx={{ paddingBottom: "5px", paddingTop: "5px" }}>
                  {member.description}
                </TableCell>
                <TableCell sx={{ paddingBottom: "5px", paddingTop: "5px" }}>
                  {member.status === null ? (
                    <Button
                      variant="contained"
                      color="primary"
                      onClick={() => createInvitation(member)}
                    >
                      초대
                    </Button>
                  ) : (
                    member.status === 'PENDING' ? (
                      <Button
                        variant="contained"
                        color="error"
                        onClick={() => deleteInvitation(member)}
                      >
                        취소
                      </Button>
                    ) : (
                      member.status === 'ACCEPTED' ? '가입완료' : '거절' 
                    ) 
                  )}
                </TableCell>
              </TableRow>
              ))}
            </TableBody>
          </Table>
          {memberList && (
            <Pagination
              count={Math.ceil(memberList.length / pageSize)}
              page={page}
              color="primary"
              onChange={handlePageChange}
              sx={{ height: "50px", display: "flex", justifyContent: "center" }}
            />
          )}
        </TableContainer>
      </Box>
    </Box>
  );
  
// ...
  • 책의 내용과는 상관 없는 작성자 본인의 토이 프로젝트 코드이다.

 

  • 입력 폼에 문자를 입력할 때마다 상태를 갱신한다.
  • 참고로 상태가 갱신될 때마다 DB를 조회하는 실시간 조회 방식으로 작성되어 있다.
GIF 파일로 만들어 봤는데 화질구지네..

 

이미지 출처

[React] Component 를 사용하는 기본적인 방법

컴포넌트는 React의 핵심 개념 중 하나이며, 이는 사용자 인터페이스(UI)를 구축하는 기반이다. 컴포넌트를 만들어보자. 우선 여기까지는 순수 JS의 선언 및 정의에 대한 구문이다. 리액트는 컴포

velog.io

소플 - soaple.io

소플

www.soaple.io