React(13) - UserList ๋ง๋ค๊ธฐ ๋ฏธ๋ ์น ๋ง๋ค๊ธฐ
๐ AddUser.js
import React from "react";
import Card from '../UI/Card';
import classes from './AddUser.module.css';
import Button from './UI/Button';
import {useState} from 'react';
import ErrorModal from "../UI/ErrorModal";
const AddUser = props =>{
const [enteredUsername, setEnteredUsername] = useState('');
const [enteredAge, setEnteredAge] =useState('');
const [error, setError] = useState();
const addUserHandler =(event) =>{
event.preventDefault();
//trim() : ์ ๋ค ๊ณต๋ฐฑ ์ ๊ฑฐ(์ ํจ์ฑ ๊ฒ์ฌ)
if(enteredUsername.trim().length ===0 || enteredAge.trim().length ===0) {
setError({
title : 'Invalid input',
message : 'Please enter a valid name and age'
})
return ;
}
if(+enteredAge < 1) {
setError({
title : 'Invalid input',
message : 'Please enter a valid name and age'});
return ;
}
//์ด๊ธฐํ ๋ก์ง
props.onAddUser(enteredUsername, enteredAge);
console.log(enteredUsername + ' ' + enteredAge)
setEnteredUsername('');
setEnteredAge('');
};
const usernameChangeHandler = (event) =>{
setEnteredUsername(event.target.value);
}
const ageChangeHandler=(event)=>{
setEnteredAge(event.target.value);
}
const errorHandler =() =>{
setError(null);
}
return(
<div>{error && <ErrorModal title={error.title} message={error.message }
onConfirm={errorHandler}/>}
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input id="username" value={enteredUsername} type="text" onChange={enteredUsername} />
<label htmlFor="userage">Age(Years)</label>
<input id="userage" value={ageChangeHandler} type="text" onChange={enteredAge} />
<Button type="submit">Add User</Button>
</form>
</Card></div>
);
};
export default AddUser;
๐ธ ํ์ํ ๋ชจ๋ ์ํฌํธํด์ค๊ธฐ
import React from "react";
import Card from '../UI/Card'; // Card ์ปดํฌ๋ํธ๋ฅผ ์ชผ๊ฐ๋์๊ณ , UI ๋ชจ๋์ ์ฌ์ฌ์ฉ์ ์ํ ์ปดํฌ๋ํธ์ด๋ค.
import classes from './AddUser.module.css';
import Button from './UI/Button';
import { useState } from 'react';
import ErrorModal from "../UI/ErrorModal"; //์๋ฌ๋ฅผ ํํํ๋ ๋ชจ๋ฌ์ฐฝ ๊ฐ์ ธ์ค๊ธฐ
๐ธ ์ด๊ธฐ๊ฐ, ์ ๋ฐ์ดํธ ์ค์ = useState
- ํํ : const [๋ณ์, ๋ณ์๋ณ๊ฒฝํจ์] = useState('๋ณ์ ์์ ์ค์ ๋๋ ์ด๊ธฐ๊ฐ');
// enteredUsername, enteredAge๋ ๊ฐ๊ฐ input์ ๋ฃ์ด์ง๋ ์ฌ์ฉ์์ ์ด๋ฆ๊ณผ ๋์ด๋ฅผ ์๋ฏธํจ. ์ด๋ ์ฌ์ฉ์์ ๋์ด์ ์ด๋ฆ์ update๋์ด์ผ ํ๋ฏ๋ก, ์ด๊ธฐ๊ฐ ์ค์ ๋ฐ ๋ณ๊ฒฝ์ ์ํ์ฌ useState๋ก ๊ด๋ฆฌํ๋ค.
// error ๋ฉ์ธ์ง๊ฐ ์ํฉ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์์ผ๋ฉฐ, ์ถ๋ ฅ ๋ฉ์ธ์ง ๊ฐ๊ณผ ํ์ดํ์ด ๋ณ๊ฒฝ๋ ์ ์์ผ๋ฏ๋ก ์ด ์ญ์ useState๋ก ๊ด๋ฆฌํ๋ค.
const [enteredUsername, setEnteredUsername] = useState('');
const [enteredAge, setEnteredAge] = useState('');
const [error, setError] = useState();
๐ ํ๋ฉด์ ์ถ๋ ฅ๋๋ ์ปดํฌ๋ํธ ์์ฑํ๊ธฐ
return(
<div>{error && <ErrorModal title={error.title} message={error.message} onConfirm={errorHandler} />}
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input id="username" value={enteredUsername} type="text" onChange={enteredUsername} />
<label htmlFor="userage">Age(Years)</label>
<input id="userage" value={ageChangeHandler} type="text" onChange={enteredAge} />
<Button type="submit">Add User</Button>
</form>
</Card></div>
);
๐ error ๋ชจ๋ฌ์ฐฝ ์ถ๋ ฅ ์ ์
{error && <ErrorModal title={error.title} message={error.message} onConfirm={errorHandler} />}
- {error && ...}์ error๊ฐ truthy ๊ฐ์ผ ๋๋ง ๊ทธ ์ดํ์ ํํ์์ ์คํํ๊ฒ ๋๋ค. ์ฆ, &&์ ์๋ฏธ๋ && ์์ ๋ถ์ ๋ถ๋ถ์ด ์ฐธ์ธ ๊ฒฝ์ฐ์ ๋ค์์๊ฒฐ๊ณผ ๊ฐ์ ๋์ถํ ์ ์๋ , if true๊ฐ ๋๋ค.
- onConfirm์ ๋ด์ฅ๋ ํน์ฑ์ด ์๋, ๋ง๋ค์ด๋ธ ํน์ฑ์ด๋ค. ๋ฐ๋ผ์ onConfirm์ ์ ์ํด์ฃผ์ด์ผ ๋๋๋ฐ onConfirm์ errorHandlerํจ์๋ฅผ ํธ์ถํ ์ ์๋๋ก ์์ฑ๋ ์์ฑ์ด๋ค. (AddUser.js์์ ๋ง๋ค์ด๋ธ ์์ฑ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋จ) error ๋ณ์๊ฐ ์กด์ฌํ๋ฉด ErrorModal ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ณ , ์กด์ฌํ์ง ์์ผ๋ฉด null์ ๋ฐํํ์ฌ ์๋ฌด๊ฒ๋ ๋ ๋๋งํ์ง ์๋ ๊ฒ์ด๋ค. ์ฆ ์๋์์ ErrorModal.js์ ๋ํด์ ์ค๋ช ํ๊ฒ ์ง๋ง,
๐ input ํ๊ทธ์ ์ํ๊ด๋ฆฌ
<input id="username" value={enteredUsername} type="text" onChange={usernameChangeHandler} />
<label htmlFor="userage">Age(Years)</label>
<input id="userage" value={enteredUserage} type="text" onChange={ageChangeHandler} />
<Button type="submit">Add User</Button>
๐ addUserHandler
- ํผ์ submit ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ฉฐ, ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํํ ํ์ ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํ๊ณ ์ ๋ ฅ ํ๋๋ฅผ ์ด๊ธฐํํ๋ค. ์ด๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ ์๋์ if ์กฐ๊ฑด๋ฌธ๋ค์ด๋ค.
- ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ๋ฉด setError ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฌ๋ฅผ ์ค์ (errorModal ๋ฌธ๊ตฌ๋ฅผ ์์ฑํจ)
const addUserHandler = (event) => {
event.preventDefault();
// trim() : ์๋ฐ์คํฌ๋ฆฝํธ ๋ด์ฅํจ์๋ก์, ๋ฌธ์์ด์ ์๊ณผ ๋ค์ ๊ณต๋ฐฑ์ ์ ๊ฑฐํด์ค๋ค.
// || : and์ ๊ฐ์
if (enteredUsername.trim().length === 0 || enteredAge.trim().length === 0) {
setError({
title: 'Invalid input',
message: 'Please enter a valid name and age'
});
return;
}
if (+enteredAge < 1) {
setError({
title: 'Invalid input',
message: 'Please enter a valid name and age'
});
return;
}
props.onAddUser(enteredUsername, enteredAge);
console.log(enteredUsername + ' ' + enteredAge)
setEnteredUsername('');
setEnteredAge('');
};
๐ญ event.preventDefault();
- addUserHandler ํจ์์์ ํผ์ submit ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ ์ ํธ์ถ๋ฉ๋๋ค. ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ํผ์ด ์ ์ถ๋ ๋ ํ์ด์ง๊ฐ ๋ค์ ๋ก๋๋๋ ๋์์ ๋ง๊ณ , ์ฌ์ฉ์๊ฐ ์ ์ํ ๋์์ธ ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํ๋ ๋ก์ง์ ์คํํ ์ ์๋๋ก ํ๋ค. ์ฃผ๋ก form ํ๊ทธ์ submit ์์ฑ์ด ๋ถ๊ฒ ๋ ๋ ์ฌ์ฉํ๋ค.
๐ญ props.onAddUser(enteredUsername, enteredAge)
- props ๊ฐ์ฒด๋ก ์ ๋ฌ๋ onAddUser ํจ์๋ฅผ ํธ์ถํ๋ ๋ถ๋ถ์ด๋ค.
- enteredUsername๊ณผ enteredAge๋ ํ์ฌ ์ ๋ ฅ๋ ์ฌ์ฉ์๋ช ๊ณผ ๋์ด๋ฅผ ๋ํ๋ด๋ฉฐ, ์ด ๊ฐ์ onAddUser ํจ์์ ์ ๋ฌํ์ฌ ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํ ์ ์๋ค. ์ฌ๊ธฐ์ onAddUser์ props์ ์ข ์๋ ํจ์ ๊ฐ์ฒด์ด๋ฉฐ, ์ด๊ฒ์ addUserHandler์์ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. ์ฆ ๋ถ๋ชจ์ปดํฌ๋ํธ์์ (AddUser.js๋ฅผ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ์์) ์ด ํจ์๋ฅผ ์ ์ํด์ผ ํจ
- enteredUsername, entereAge๋ input value์ ์์ฑ๋ ์ ๋ ฅ๋ณ์๋ค์ด๊ณ ์ด ์น๊ตฌ๋ค์ ๊ทธ๋๋ก ๊ฐ์ ธ์์ onAddUserํจ์์์ ์ฒ๋ฆฌํจ.
๐ญ setEnteredUsername(''); setEnteredAge('');
- ์ ๋ ฅ ํ๋๋ฅผ ์ด๊ธฐํํ๋ ๋ถ๋ถ
- enteredUsername ์ํ๋ฅผ ๋น ๋ฌธ์์ด๋ก ์ค์ ํ์ฌ ์ฌ์ฉ์๋ช ์ ๋ ฅ ํ๋๋ฅผ ๋น์ฐ๊ณ , setEnteredAge('')๋ enteredAge ์ํ๋ฅผ ๋น ๋ฌธ์์ด๋ก ์ค์ ํ์ฌ ๋์ด ์ ๋ ฅ ํ๋๋ฅผ ๋น์ฐ๊ฒ ๋์ด ๋ค์ ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํ ์ ์๋๋ก ํ๋ค.
- +enteredAge๋ enteredAge ๋ณ์๋ฅผ ์ซ์๋ก ํ๋ณํํ๋ ๋ถ๋ถ์ผ๋ก, enteredAge๋ ๋ฌธ์์ด ํํ๋ก ์ ๋ ฅ๋ ๋์ด ๊ฐ์ด๊ธฐ ๋๋ฌธ์, + ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ ์ซ์๋ก ๋ณํํด์ผ ํ๋ค.
๐ UI
๐ ErrorModal.js
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}></div>
<Card className={classes.modal}>
<header className={classes.header}>
<h2>{props.title}</h2>
</header>
<div className={classes.content}>
<p>{props.message}</p>
</div>
<fotter className={classes.action}>
<Button onClick={props.onConfirm}>Okay</Button>
</fotter>
</Card>
</div>
);
};
export default ErrorModal;
- ์ฌ์ค... ErrorModal ์ปดํฌ๋ํธ์ ๋ํด์ ์ค๋ช ํ ๊ฒ์ ๋ฑํ ์๋ค. ๊ฐ์ธ์ ์ผ๋ก css.Module ํํ๋ก ํ๋ก๋ํธ ๊ด๋ฆฌ๋ฅผ ์ํ์ง๋ ์๊ธฐ๋ ํ๊ณ , ๋ณดํต ์คํ์ผ์ปดํฌ๋ํธ, StoryBook, Antd, Reactstrap ๋ฑ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ถ๋ฌ์์ css styling์ ํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๊ธฐ ๋๋ฌธ์ classes.action ,clsses.header ๋ฑ์ ์ค๋ช ์ ์์ธํ๊ฒ ๋ค๋ฃจ์ง๋ ์๋๋ก ํ๋ค. ๊ฐ๋จํ๊ฒ ๋งํ์๋ฉด, Error.cssํ์ผ์ ์ด๋ฆ์ Error.module.css๋ก ์ด๋ฆ์ ๋ฐ๊ฟ์ค๋ค์์, ์ฌ๊ธฐ์ . ๋ฑ์ผ๋ก ์ ์๋ ์คํ์ผ๋ง์ ์ด๋ฆ์ ๋ถ์ฌ html ํ๊ทธ์ className = {classes.์คํ์ผ๋ง์ด๋ฆ}์ผ๋ก className์ ์ง์ ํ๋ ๊ฒ์ด๋ค.
- backdrop: ๋ฐฐ๊ฒฝ์ ๋ถํฌ๋ช ํ๊ฒ ๊ฐ๋ฆฌ๋ ์ญํ ์ ํ๋ div ์์์ด๋ฉฐ ๋ชจ๋ฌ ์ฐฝ ์ธ๋ถ๋ฅผ ํด๋ฆญํ๋ฉด props.onConfirm ํจ์๊ฐ ์คํ๋๋ค.
- Button: ํ์ธ ๋ฒํผ์ผ๋ก ์ฌ์ฉ๋๋ ์ปดํฌ๋ํธ์ด๋ฉฐ props.onConfirm ํจ์๊ฐ ์คํ๋๋๋ก ์ค์ ๋์ด ์๋ค.
๐ Card.js
import React from "react";
import classes from './Card.module.css';
const Card = props =>{
//Card.module.css์ card๊ฐ์ ธ์ค๊ณ ๊ฑฐ๊ธฐ์ ์๋ css styling์ ์ฌ์ฉํ๋ค๋ ์๋ฏธ
return <div className={`${classes.card} ${props.className}`}>{props.children}</div>
};
export default Card;
- ์ฌ๊ธฐ์๋ ๋ฑํ ๋ณผ๊ฑด ์์ง๋ง..... ${classes.card} ${props.className}์ ์ฌ์ฉํ์ฌ Card.module.css ํ์ผ์์ ์ ์๋ card ํด๋์ค์ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์ ๋ฌ๋ className ํ๋กํผํฐ๋ฅผ ๊ฒฐํฉํ์ฌ ํด๋์ค๋ฅผ ์ ์ฉํด์ค๋ค. ์ด๋ฅผ ํตํด ์ปดํฌ๋ํธ์ ์คํ์ผ๋ง์ ์ง์ ํ ์ ์๋ค.
- props.children: ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ Card ์ปดํฌ๋ํธ๋ก ์ ๋ฌ๋ ์์ ์์๋ค์ ๋ ๋๋งํด์ค๋ค. ์ด๋ฅผ ํตํด Card ์ปดํฌ๋ํธ ์์ ํฌํจ๋ ๋ด์ฉ์ ํ์ํ ์ ์๋ค.
๐ 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>
}
- ์์ ๋ง์ฐฌ๊ฐ์ง๋ก props๋ฅผ ์ธ์๋ก ๋ฐ์์ค๊ณ ์์ : ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์์ฑ์ ์ ์ฉํ๊ธฐ ์ํด์
- type ์์ฑ: props.type์ ์ฌ์ฉํ์ฌ ๋ฒํผ์ ํ์ ์ ์ง์ ํด์ค๋ค. props.type์ด ์ฃผ์ด์ง์ง ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ์ผ๋ก 'button'์ ์ฌ์ฉํ๋ค. <Button type="submit">Add User</Button>์์์ ํ์ ์ ๋ฐ์์ค๊ฒ ๋๋ ๊ฒ์ด ๊ทธ ์์์
- onClick ์ด๋ฒคํธ ํธ๋ค๋ฌ: props.onClick์ ์ฌ์ฉํ์ฌ ํด๋ฆญ ์ด๋ฒคํธ์ ๋ํ ํธ๋ค๋ฌ๋ฅผ ์ง์ ํด์ค๋ค. ์ด๋ฅผ ํตํด ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ๋ฒํผ ํด๋ฆญ์ ๋ํ ๋์์ ์ ์ํ ์ ์๋ค.
๐ UserList.js
- props๋ผ๋ ๊ฐ์ฒด์ users ์์ฑ์ ์๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ฆฌ์คํธ ํํ๋ก ์ถ๋ ฅํ๋ ๋ถ๋ถ์. ์ด๋ props๋ App.js๋ฅผ ์๋ฏธํ๋ค. App.js์์ UserList.js๋ฅผ ์ํฌํธํด์ค๊ธฐ ๋๋ฌธ์ด๋ค.
- users ๋ฐฐ์ด์ ๊ฐ ์์์ ๋ํด ๋ฐ๋ณตํ๋ฉฐ, ๋ฐฐ์ด์ ๊ฐ ์์๋ฅผ ๋ฐ์์์ ํจ์๋ฅผ ์คํํฉ๋๋ค. ์ด ๋, user ๋งค๊ฐ๋ณ์๋ ํ์ฌ ๋ฐ๋ณต ์ค์ธ users ๋ฐฐ์ด ์์๋ฅผ ๋ํ๋ด๋ ๊ฒ์ด๋ค.
- props.๋ฐฐ์ด์ด๋ฆ.map((๋ฐฐ์ด์์์ด๋ฆ : ์๋ฌด๊ฑฐ๋ ํ๋๋ฐ, ๋ณดํต users, elements์ด๋ฏ๋ก element๋ผ๊ณ ํจ) => (์ถ๋ ฅํํ : {๋ฐฐ์ด์์์ด๋ฆ.์์ฑ1} {๋ฐฐ์ด์์์ด๋ฆ.์์ฑ2}...))
import React from "react";
import Card from "../UI/Card";
import classes from './UserList.module.css';
const UsersList = props =>{
return (
<Card className = {classes.users}>
<ul>
{props.users.map((user) =>( <li key={user.id}>{user.name} ({user.age} years old)</li>))}
</ul>
</Card>
);
};
export default UsersList;
- {user.name}์ user ๊ฐ์ฒด์ name ์์ฑ์ ๋ํ๋ด๋ฉฐ, ์ฌ์ฉ์์ ์ด๋ฆ์ ์ถ๋ ฅํ๋ค.
- {user.age}๋ user ๊ฐ์ฒด์ age ์์ฑ์ ๋ํ๋ด๋ฉฐ, ์ฌ์ฉ์์ ๋์ด๋ฅผ ์ถ๋ ฅํ๋ค.
- ์ฆ ๋ฐฐ์ด ์์์ ์ถ๋ ฅ ํํ๋ {๋ฐฐ์ด์ ์์.์์ฑ} ์ด ํํ๋ก ์ถ๋ ฅํด์ผ ํ๋ฉด์์ ์ถ๋ ฅ๋๋ค.
- <li key={user.id}></li> : key ์์ฑ์ React์์ ๋ฆฌ์คํธ ํญ๋ชฉ์ ๋ ๋๋งํ ๋ ํ์ํ ๊ณ ์ ์๋ณ์์ด๋ค. ๋ฐ๋์ json ํํ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋ ์์ฑํด์ค์ผ ์ค๋ฅ๊ฐ ๋จ์ง ์๋๋ค. ์ฌ๊ธฐ์๋ user.id ๊ฐ์ ์ฌ์ฉํ์ฌ ๊ฐ ํญ๋ชฉ์ ๊ตฌ๋ณํ๋ค.
App.js
import React from 'react';
import AddUser from './components/Users/AddUser';
import UsersList from './components/Users/UsersList';
import {useState} from 'react';
const App=() =>{
const [usersList, setUsersList] = useState([]);
const addUserHandler = (userName, userAge) =>{
setUsersList((prevUserlist)=>{
return [...prevUserlist, {name : userName, age : userAge, id : Math.random().toString()}];
});
}
return (
<div>
<AddUser onAddUser={addUserHandler} />
<UsersList users={usersList}/>
</div>
)
}
export default App;