연습 프로젝트는
- username과 age를 입력해서 리스트로 보여주고,
- 만약 둘 중 하나라도 입력되지 않은 경우, 혹은 age가 음수로 작성된 경우에는 경고 모달창을 띄우는 기능을 가진다.
1. User 정보 입력 폼 구성
AddUser.js
import React from "react";
const AddUser = (props) => {
const addUserHandler = (event) => {
event.preventDefault();
};
return (
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input id="username" type="text" />
<label htmlFor="age">Age (Years)</label>
<input id="age" type="number" />
<button type="submit">Add User</button>
</form>
);
};
export default AddUser;
여기서 주의깊게 봐야하는 부분은 label의 htmlFor속성이다. html코드에서는 label의 for속성에 연결하고자 하는 element의 id값을 넣어주었는데, JSX코드에서는 이 for속성을 htmlFor로 작성해주어야 한다.
2. Wrapping component 구현
다른 컴포넌트들을 감싸는 Card 컴포넌트를 만들어보자.
Card.js
import React from "react";
import classes from "./Card.module.css";
const Card = (props) => {
return (
<div className={`${classes.card} ${props.className}`}>{props.children}</div>
);
};
export default Card;
Wrapping component는 props.children으로 감싸고 있는 element들을 출력할 수 있다.
또 주의해서 봐야할 점은 css모듈로 스타일링을 할 때, card.module.css뿐만 아니라 Card컴포넌트를 사용하는 다른 외부 컴포넌트에서 Card컴포넌트로 스타일링을 적용하기 위해 className 속성을 위와 같이 작성해주어야 한다.
AddUser.js
import React from "react";
import Card from "../UI/Card";
import classes from "./AddUser.module.css";
const AddUser = (props) => {
const addUserHandler = (event) => {
event.preventDefault();
};
return (
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input id="username" type="text" />
<label htmlFor="age">Age (Years)</label>
<input id="age" type="number" />
<button type="submit">Add User</button>
</form>
</Card>
);
};
export default AddUser;
Card컴포넌트에 props.className으로 스타일링을 패싱하고 있다. 속성 이름을 "className"으로 반드시 해야하는 것은 아니고 원하는 대로 직접 작성하면 되는데, 이때 Card컴포넌트에서 props로 받아오는 속성 이름도 동일하게 변경해주어야 한다.
3. 재사용 Button 컴포넌트 구현
이번엔 기존에 <button> 기본 element로 구현했던 버튼을 직접 만들어서 적용해보려고 한다.
Button.js
import React from "react";
import classes from "./Button.module.css";
const Button = (props) => {
return (
<button
className={classes.button}
type={props.type || "button"}
onClick={props.onClick}
>
{props.children}
</button>
);
};
export default Button;
props로 type과 onClick함수 등의 내용을 받아서 적용한다. 또 css모듈로 스타일링도 주었다.
그리고 AddUser.js에서 쓰인 <button>을 직접 만든 <Button>으로 바꾸어주자. props 속성 이름들을 기본 button 태그의 속성과 동일하게 작성해주어서 b->B 바꾸기만 하면 된다.
<Button type="submit">Add User</Button>
4. 사용자 State 관리
사용자 정보를 state으로 관리해보자.
- import {useState} from 'react'
- [현재 상태, 상태 설정]=useState('')
- input에 onChange 이벤트 리스너 달아 이벤트 발생 시 state 새로 설정
import React, { useState } from "react";
.............
const AddUser = (props) => {
const [enteredUsername, setEnteredUsername] = useState("");
const [enteredAge, setEnteredAge] = useState("");
const usernameChangeHandler = (event) => {
setEnteredUsername(event.target.value);
};
const ageChangeHandler = (event) => {
setEnteredAge(event.target.value);
};
..................
return (
..............
<input id="username" type="text" onChange={usernameChangeHandler} />
<label htmlFor="age">Age (Years)</label>
<input id="age" type="number" onChange={ageChangeHandler} />
..............
);
};
export default AddUser;
5. 검증 추가 및 로직 재설정
이번엔 사용자 정보 입력 후 add user 버튼을 누르면 input입력 칸을 clear(초기화)하고, 입력값이 적절하지 않은 경우(입력하지 않았거나, age가 음수인 경우: 유효성 검사, validation)를 판별하는 로직을 추가할 것이다.
먼저, 초기화 로직부터 살펴보자.
- add user버튼이 눌렸을 때, state을 ''(빈칸)으로 set: 버튼의 onClick handler 수정
- 초기화한 state을 <input>태그에 반영: input에 value속성 값으로 state 지정
..............
const addUserHandler = (event) => {
event.preventDefault();
console.log(enteredUsername, enteredAge);
setEnteredUsername("");
setEnteredAge("");
};
...............
return (
................
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
onChange={usernameChangeHandler}
value={enteredUsername}
/>
<label htmlFor="age">Age (Years)</label>
<input
id="age"
type="number"
onChange={ageChangeHandler}
value={enteredAge}
/>
................
);
};
export default AddUser;
이렇게 하면 add user버튼을 클릭하면 <input>태그들이 빈칸으로 초기화된다.
다음으로 유효성 검사를 살펴보자. add user버튼이 클릭되면 그때 저장된 state들이 유효한지 확인하는 것으로 버튼의 onClick 이벤트 함수 안에 해당 로직을 추가하면 된다.
const addUserHandler = (event) => {
event.preventDefault();
if (enteredUsername.trim().length === 0 || enteredAge.trim().length === 0) {
return;
}
if (+enteredAge < 1) {
return;
}
console.log(enteredUsername, enteredAge);
setEnteredUsername("");
setEnteredAge("");
};
return; 을 만나면 그 뒤에 코드들이 실행되지 않고 해당 메소드를 빠져나간다.
enteredAge 앞에 '+'를 붙여준 이유는 enteredAge가 문자열로 저장되었기 때문에 이를 number로 확실하게 바꿔주기 위함이다(자바스크립트는 + 없어도 알아서 비교되긴 함).
6. user data list 컴포넌트
이번엔 user list를 보여주는 컴포넌트, UsersList를 만들어보자.
UsersList 컴포넌트는 props로 사용자 정보 배열(users)를 받아오고 그것을 렌더링하여 보여준다.
import React from "react";
import Card from "../UI/Card";
import classes from "./UsersList.module.css";
const UsersList = (props) => {
return (
<Card className={classes.users}>
<ul>
{props.users.map((user, index) => (
<li>
{user.name}({user.age} years old)
</li>
))}
</ul>
</Card>
);
};
export default UsersList;
자바스크립트 map 메소드를 이용하여 users배열의 각 요소마다 <li>태그 안에 그 정보를 담아 출력한다.
import React from "react";
import AddUser from "./components/Users/AddUser";
import UsersList from "./components/Users/UsersList";
function App() {
return (
<div>
<AddUser />
<UsersList users={[]} />
</div>
);
}
export default App;
일단 App컴포넌트에서 UsersList컴포넌트에 빈 배열을 넘겨주었다.
7. state 이용한 user list 관리
새로 입력한 사용자 목록을 렌더링하여 보여주려면, AddUser컴포넌트에서 입력한 사용자 정보를 사용자 목록에 업데이트하여 사용자 목록을 출력하는 UsersList컴포넌트로 전달해주어야 한다.
이때, 컴포넌트 트리 구조를 생각해보면 AddUser와 UsersList 사이에는 직접적인 연결이 없고, 둘 다 App컴포넌트 아래에 존재하기 때문에 App을 거쳐서 user list를 전달해야만 한다.
- App 컴포넌트에 사용자 정보 배열(usersList)을 관리하는 state 생성
- 해당 state을 UsersList 컴포넌트에 전달
- 사용자 정보 배열(usersList)에 새 데이터 추가하는 메소드 작성
- 데이터 추가 메소드를 AddUser컴포넌트에 패싱
import React, { useState } from "react";
import AddUser from "./components/Users/AddUser";
import UsersList from "./components/Users/UsersList";
function App() {
const [usersList, setUserList] = useState([]);
const addUserHandler = (uName, uAge) => {
setUserList((prevUsersList) => {
return [...prevUsersList, { name: uName, age: uAge }];
});
};
return (
<div>
<AddUser onAddUser={addUserHandler} />
<UsersList users={usersList} />
</div>
);
}
export default App;
위와 같이 작성하고 나서,
props.onAddUser(enteredUsername, enteredAge);
AddUser.js에서는 props로 전달받은 메소드에 입력된 정보들을 넘겨준다.
이렇게 하면 이제 추가한 항목이 목록에 제대로 렌더링되는데,
이런 warning이 뜬다. 전에도 본 것인데 UsersList컴포넌트에서 데이터들을 map로 매핑하여 출력할 때 element에 key속성을 지정하지 않았기 때문이다.
그래서 데이터에 id속성을 추가했다.
//App.js
const addUserHandler = (uName, uAge) => {
setUserList((prevUsersList) => {
return [
...prevUsersList,
{ name: uName, age: uAge, id: Math.random().toString() },
];
});
};
그리고 UsersList에서 데이터를 <li>형태로 매핑할 때 key값으로 고유한 값인 id(완전히 unique하다고는 못하지만, 거의!)를 주면,
//UsesList.js
<ul>
{props.users.map((user, index) => (
<li key={user.id}>
{user.name}({user.age} years old)
</li>
))}
</ul>
더 이상 warning이 뜨지 않는다!
8. Error Modal 컴포넌트 생성 및 출력
이번엔 입력값이 유효하지 않은 경우 브라우저에 뜨는 모달창을 컴포넌트로 만들어보자.
import React from "react";
import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";
const ErrorModal = (props) => {
return (
<div>
<div className={classes.backdrop} />
<Card className={classes.modal}>
<header className={classes.header}>
<h2>{props.title}</h2>
</header>
<div className={classes.content}>
<p>{props.message}</p>
</div>
<footer className={classes.actions}>
<Button>Okay</Button>
</footer>
</Card>
</div>
);
};
export default ErrorModal;
Card와 Button 컴포넌트를 재사용하여 만들었다. 또 css모듈을 이용해 각 element에 스타일을 적용했다.
ErroModal 컴포넌트 역시 재사용 가능하게 할 것이므로 props를 이용해 그 내용을 동적으로 구현해준다.
.backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
z-index: 10;
background: rgba(0, 0, 0, 0.75);
}
.modal {
position: fixed;
top: 30vh;
left: 10%;
width: 80%;
z-index: 100;
overflow: hidden;
}
.header {
background: #4f005f;
padding: 1rem;
}
.header h2 {
margin: 0;
color: white;
}
.content {
padding: 1rem;
}
.actions {
padding: 1rem;
display: flex;
justify-content: flex-end;
}
@media (min-width: 768px) {
.modal {
left: calc(50% - 20rem);
width: 40rem;
}
}
그 중 backdrop은 브라우저에 모달창이 뜬 그 뒷 배경을 처리해주는 스타일 클래스이다.
그리고 ErrorModal 컴포넌트를 AddUser(여기서 입력값 유효성 검사를 실시하기 때문에)에서 띄울 것이다.
return (
<div>
<ErrorModal title="An error occured!" message="Somethin went wrong!" />
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
onChange={usernameChangeHandler}
value={enteredUsername}
/>
<label htmlFor="age">Age (Years)</label>
<input
id="age"
type="number"
onChange={ageChangeHandler}
value={enteredAge}
/>
<Button type="submit">Add User</Button>
</form>
</Card>
</div>
);
AddUser컴포넌트에서 JSX코드만 가져온 것이다. <div>로 크게 감싸고 그 안에 ErrorModal 컴포넌트를 추가해주었다.
9. 오류 State 관리
이제 유효성 검사를 통과하지 못한 경우 오류 모달창을 띄우는 로직을 알아보자.
- 오류 여부 state 생성
- 오류가 발생한 경우, state 설정
- 오류 state인 경우, 모달창 출력
- 모달창의 okay버튼 혹은 모달창 백그라운드 클릭 시엔 모달창 다시 없애기: state 조건
먼저 오류가 발생한 경우에만 모달창이 뜨는 것이므로 오류 state을 생성해야한다.
const [error, setError] = useState();
모달창을 사용하는 AddUser 컴포넌트에 error state을 생성하였다.
그리고 오류가 발생한 경우, 이 state을 오류 상태로 바꾸어준다.
const addUserHandler = (event) => {
event.preventDefault();
if (enteredUsername.trim().length === 0 || enteredAge.trim().length === 0) {
setError({
title: "InValid input",
message: "Please enter a valid name and age (non-empty values)",
});
return;
}
if (+enteredAge < 1) {
setError({
title: "InValid age",
message: "Please enter a valid age (>0)",
});
return;
}
props.onAddUser(enteredUsername, enteredAge);
setEnteredUsername("");
setEnteredAge("");
};
유효성 검사를 하는 addUserHandler에서 조건문에 따라 적절히 state을 설정해준다. error state을 ErrorModal 컴포넌트에 표시할 title과 message로 적절히 작성하여 객체로 설정하였다.
또 JSX코드를 아래와 같이 수정하여
{error && (
<ErrorModal
title={error.title}
message={error.message}
/>
)}
error가 존재하는 경우에만! ErrorModal이 뜨도록 조건을 걸어주었다.
이렇게 하면 에러가 발생한 경우 모달창이 뜨는데, 이 모달창을 없애는 방법은 error을 undefined나 null로 설정해주는 것이다.
const errorHandler = () => {
setError(null);
};
따라서 AddUser 컴포넌트에 이렇게 error 상태를 null로 설정하는 함수를 작성하였다.
그리고
{error && (
<ErrorModal
title={error.title}
message={error.message}
onConfirm={errorHandler}
/>
)}
JSX코드를 다시 수정하여 작성한 에러 초기화 함수를 props로 넘겨준다.
ErrorModal 컴포넌트에서 백그라운드나 okay 버튼이 클릭된 경우 onConfirm으로 넘겨받은 함수를 실행하면,
import React from "react";
import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";
const ErrorModal = (props) => {
return (
<div>
<div className={classes.backdrop} onClick={props.onConfirm} />
<Card className={classes.modal}>
<header className={classes.header}>
<h2>{props.title}</h2>
</header>
<div className={classes.content}>
<p>{props.message}</p>
</div>
<footer className={classes.actions}>
<Button onClick={props.onConfirm}>Okay</Button>
</footer>
</Card>
</div>
);
};
export default ErrorModal;
브라우저 백그라운드나 okay버튼이 클릭된 경우, error 상태가 null로 바뀌어 모달창이 사라진다.
모달창 구현을 혼자 못해냈는데 내용 기억하기!!
'React > (Udemy)React-The Complete Guide' 카테고리의 다른 글
[React] Fragments, Portals & "Refs"(섹션9) (0) | 2022.06.20 |
---|---|
[React] 리액트 앱 디버깅하기(섹션7) (0) | 2022.06.01 |
[React] 리액트 컴포넌트 스타일링(세션 6): styled components, css모듈 (0) | 2022.05.28 |
[React] 렌더링 리스트 및 조건부 Content(섹션5) (0) | 2022.05.21 |
[React] 리액트 state, event 처리(섹션4) (0) | 2022.05.16 |