온전히 내가 나중에 참고하려고 기록해두는 React 컴포넌트 조각들 🧩
// Example 1
const [expenses, setExpenses] = useState(DUMMY_EXPENSES);
const addExpenseHandler = (expense) => {
setExpenses((prevExpenses) => {
// [새로운 데이터, ...이전 데이터들] <- 새로운 데이터가 맨 위에 추가
return [expense, ...prevExpenses];
});
};
// Example 2
const [usersList, setUsersList] = useState([]);
const addUserHandler = (uName, uAge) => {
setUsersList((prevUsersList) => {
// [...이전 데이터들, 새로운 데이터] <- 새로운 데이터가 맨 아래에 추가
return [
...prevUsersList,
{ name: uName, age: uAge, id: Math.random().toString() },
];
});
};
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(), // 받아온 추가할 데이터에 id를 따로 추가
};
// 자식 → 부모 컴포넌트로 값 전달 로직
// onAddExpense 함수는 부모 컴포넌트에 있던 데이터 추가 함수
props.onAddExpense(expenseData);
};
const [userInput, setUserInput] = useState({
enteredTitle: "",
enteredAmount: "",
enteredDate: "",
});
const titleChangeHandler = (event) => {
// 이전 상태에 기반하는 상황에선 이 접근 방법 사용 권장!
setUserInput((prevState) => {
return { ...prevState, enteredTitle: event.target.value };
});
};
const Card = (props) => {
// 이렇게 클래스를 props로 받아오면 기본 Card 컴포 css에 다르게 구현하고 싶은 css도 추가 가능
const classes = "card " + props.className;
return <div className={classes}>{props.children}</div>;
};
export default Card;
// 위의 컴포넌트 사용은 다른 컴포넌트에서 이런 식으로!
return (
<div>
<Card className="expenses">
...
</Card>
</div>
);
// JSX 문법도 이렇게 변수에 할당해서 사용 가능!
let content = (
<p style={{ textAlign: 'center' }}>No goals found. Maybe add one?</p>
);
if (courseGoals.length > 0) {
content = (
<CourseGoalList items={courseGoals} onDeleteItem={deleteItemHandler} />
);
}
...
// 이렇게 구현하면 화면에 렌더링되는 부분 코드를 간결하게 줄일 수 있다!
return (
<div>{content}</div>
)
// class 이름에 '-'가 들어가면 적용이 안됨 (ex. styles.form-control)
// => 배열로 문자열을 감싸줘야 함 (styles['form-control'])
<div className={`${styles["form-control"]} ${!isValid && styles.invalid}`}/>
// style 내에 삼항 조건 연산자 사용할 때
<label style={{ color: !isValid ? "red" : "black" }}>Course Goal</label>
// Button.js
// css 파일은 기존 css파일과 동일하게 구성하고, 파일 이름을 Button.module.css 이런 식으로 변경
import styles from "./Button.module.css";
// 재사용 가능한 버튼 컴포넌트~!
const Button = (props) => {
return (
<button type={props.type} className={styles.button} onClick={props.onClick}>
{props.children}
</button>
);
};
export default Button;
// state로 유효성 검사를 하면 state 값이 바뀔 때마다 검사가 실행되는데
// 그게 아니라 폼 제출했을 때만 유효성 검사를 하고 싶을 땐 ref 사용하기!
// useRef의 값은 항상 객체이며, current props(그 ref가 연결된 실제 값)을 가짐
const nameInputRef = useRef();
const ageInputRef = useRef();
const [error, setError] = useState();
const addUserHandler = (event) => {
event.preventDefault(); // 기본 동작 실행 방지
const enteredName = nameInputRef.current.value;
const enteredUserAge = ageInputRef.current.value;
// trim(): 공백 제거
if (enteredName.trim().length === 0 || enteredUserAge.trim().length === 0) {
setError({
title: "Invalid input",
message: "Please enter a valid name and age (non-empty values)",
});
return;
}
if (+enteredUserAge < 1) {
setError({
title: "Invalid age",
message: "Please enter a avalid age (> 0)",
});
return;
}
props.onAddUser(enteredName, enteredUserAge);
// 값 초기화
nameInputRef.current.value = "";
ageInputRef.current.value = "";
};
return (
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input id="username" type="text" ref={nameInputRef} />
<Button type="submit">Add User</Button>
</form>
);
// store/auth-context.js
const AuthContext = React.createContext({
isLoggedIn: false,
onLogout: () => {}, // TIP! 이렇게 설정해놓으면 다른 컴포넌트에서 사용할 때 키워드 자동완성됨!
onLogin: (email, password) => {},
});
export const AuthContextProvider = (props) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
const storedLogged = localStorage.getItem("isLoggedIn");
if (storedLogged === "1") {
setIsLoggedIn(true);
}
}, []);
const logoutHandler = () => {
localStorage.removeItem("isLoggedIn");
setIsLoggedIn(false);
};
const loginHandler = () => {
localStorage.setItem("isLoggedIn", "1");
setIsLoggedIn(true);
};
return (
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
onLogout: logoutHandler,
onLogin: loginHandler,
}}
>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
// index.js
// <App/> 태그를 감싸줌으로써 전역으로 Context 사용 가능
<AuthContextProvider>
<App />
</AuthContextProvider>
// 다른 컴포넌트에서 AuthContext 사용하기
import React, { useContext } from "react";
import AuthContext from "../../store/auth-context";
const Home = (props) => {
const authCtx = useContext(AuthContext); // <- useContext로 가져다쓰면 편리!
return (
<Card>
<Button onClick={authCtx.onLogout}>Logout</Button>
</Card>
);
};
export default Home;
import React, { useState, useEffect } from "react";
const Login = (props) => {
const [enteredEmail, setEnteredEmail] = useState("");
const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState("");
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
useEffect(() => {
const identifier = setTimeout(() => {
setFormIsValid(
enteredEmail.includes("@") && enteredPassword.trim().length > 6
);
}, 500);
// cleanup function: 모든 새로운 sideEffect()가 실행되기 전에, 그리고 컴포넌트가 제거되기 전에 실행됨
// 첫번째 sideEffect()가 실행되기 전에는 실행되지 않음
return () => {
clearTimeout(identifier); // cleanup 함수가 실행되기 전에 설정된 타이머를 지움
};
}, [enteredEmail, enteredPassword]);
const emailChangeHandler = (event) => {
setEnteredEmail(event.target.value);
};
const passwordChangeHandler = (event) => {
setEnteredPassword(event.target.value);
};
const validateEmailHandler = () => {
setEmailIsValid(enteredEmail.includes("@"));
};
const validatePasswordHandler = () => {
setPasswordIsValid(enteredPassword.trim().length > 6);
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(enteredEmail, enteredPassword);
};
return (
<Card>
<form onSubmit={submitHandler}>
<div>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={enteredEmail}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
{/* onBlur: 포커스 해제될 때의 이벤트 */}
</div>
<Button type="submit" disabled={!formIsValid}>Login</Button>
</form>
</Card>
);
};
export default Login;
// Login.js
// 리듀서 함수 내부에선 컴포넌트 함수에서 만들어진 어떤 데이터도 필요하지 않기 떄문에 컴포넌트 함수 범위 밖에서 만들 수 있음
// 이메일 리듀서
const emailReducer = (state, action) => {
if (action.type === "USER_INPUT") {
return { value: action.val, isValid: action.val.includes("@") };
}
if (action.type === "INPUT_BLUR") {
return { value: state.value, isValid: state.value.includes("@") };
}
return { value: "", isValid: false };
};
// 비밀번호 리듀서
const passwordReducer = (state, action) => {
if (action.type === "USER_INPUT") {
return { value: action.val, isValid: action.val.trim().length > 6 };
}
if (action.type === "INPUT_BLUR") {
return { value: state.value, isValid: state.value.trim().length > 6 };
}
return { value: "", isValid: false };
};
// Login.js
const Login = (props) => {
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: "",
isValid: null,
});
const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
value: "",
isValid: null,
});
const authCtx = useContext(AuthContext);
const emailInputRef = useRef();
const passwordInputRef = useRef();
// 객체 구조분해할당
const { isValid: emailIsValid } = emailState; // emailIsValid는 값이 아니라 별칭!
const { isValid: passwordIsVlid } = passwordState;
// 위의 구조분해할당으로 isValid만 가져다가 아래 useEffect 내에 사용하므로 값은 변경되어도 유효성이 변경되지 않으면 useEffect가 재실행되지 않음!
useEffect(() => {
const identifier = setTimeout(() => {
setFormIsValid(emailIsValid && passwordIsVlid);
}, 500);
return () => {
clearTimeout(identifier);
};
}, [emailIsValid, passwordIsVlid]);
const emailChangeHandler = (event) => {
dispatchEmail({ type: "USER_INPUT", val: event.target.value });
};
const passwordChangeHandler = (event) => {
dispatchPassword({ type: "USER_INPUT", val: event.target.value });
};
const validateEmailHandler = () => {
dispatchEmail({ type: "INPUT_BLUR" });
};
const validatePasswordHandler = () => {
dispatchPassword({ type: "INPUT_BLUR" });
};
const submitHandler = (event) => {
event.preventDefault();
if (formIsValid) {
authCtx.onLogin(emailState.value, passwordState.value);
} else if (!emailIsValid) {
emailInputRef.current.focus();
} else {
passwordInputRef.current.focus();
}
};
return (
<Card>
<form onSubmit={submitHandler}>
<Input
ref={emailInputRef}
id="email"
label="E-Mail"
type="email"
value={emailState.value}
isValid={emailIsValid}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
<Button type="submit" className={classes.btn}>Login</Button>
</form>
</Card>
);
};
export default Login;