JiSoo's Devlog
[Udemy React 완벽 가이드] Section 10 본문
"Side Effects"란?
- 애플리케이션에서 일어나는 다른 모든 것을 뜻한다
- 예를 들면 http 리퀘스트를 보내는 것, 브라우저 저장소에 무언가를 저장하는 것, 코드에서 타이머나 간격 설정
- 즉 일반적인 컴포넌트 함수 밖에서 일어나는 일
- useEffect 훅 -> 두 개의 매개변수, 두 개의 인수와 같이 호출된다
- 첫 번째 인수는 함수로 지정된 의존성이 변경된 경우라면 모든 컴포넌트 평가 후에 실행되어야 하는 함수이다
- 두 번째 인수로는 지정된 의존성을 넣어줘야 하는데 이것은 의존성으로 구성된 배열이다
- 의존성이 변경될 때마다 첫 번째 함수가 다시 실행된다
- 첫 번째 함수에 어떤 사이드 이펙트 코드라도 넣을 수 있는데 그러면 그 코드는 우리가 지정한 의존성이 변경된 경우에만 실행되고 컴포넌트가 다시 렌더링 될 때는 실행되지 않는다
useEffect(() => { ... }, [ dependencies ]);
useEffect() 훅 사용하기
- 브라우저에 내장된 저장 메커니즘은 리액트와 관련 없다
- 로컬 저장소를 실행할 수 있는데 setItem은 브라우저에서 사용할 수 있는 전역 객체이다
localStorage.setItem('isLoggedIn', '1');
- setItem 안에 아무 식별자를 넣으면 되는데 문자열이기만 하면 된다
- 두 번째 인자도 문자열이어야 하는데 예를 들어 로그인했다는 의미로 1이나 0을 써도 되고 logged 이런 식으로 문자열을 써도 된다
- 저장되어 있는지 확인하고 저장되어 있으면 setIsLoggedIn을 true로 설정하려면 아래 코드를 사용하면 되긴 하지만 무한 루프의 가능성 때문에 문제가 되기에 useEffect를 사용해 아래 코드가 언제 실행될지 제어할 수 있게 해야 한다
const storedUserLoggedInInformation = localStorage.getItem("isLoggedIn");
if (storedUserLoggedInInformation === "1") {
setIsLoggedIn(true);
}
- useEffect를 사용하면 함수가 리액트에 의해 실행되기 때문에 모든 컴포넌트 재평가 후에 실행된다
useEffect(() => {
const storedUserLoggedInInformation = localStorage.getItem("isLoggedIn");
if (storedUserLoggedInInformation === "1") {
setIsLoggedIn(true);
}
}, []);
- 앱이 시작되면 이 코드가 실행되고 state가 업데이트되고 컴포넌트 함수가 다시 실행되도록 트리거 된다
- 데이터 가져오기는 사이드이펙트이고 UI와 직접적인 관련이 없다
useEffect & 종속성
useEffect(() => {
setFormIsValid(
enteredEmail.includes("@") && enteredPassword.trim().length > 6
);
}, [setFormIsValid, enteredEmail, enteredPassword]);
- setFormIsValid를 실행하지 않아야 하는데 실행하면 그 결과가 의존성으로 추가되기 때문이다
- 포인터로 추가하면 본질적으로 함수 그 자체를 의존성으로 추가하는 게 된다
- 이렇게 하면 모든 로그인 컴포넌트 실행 후에 useEffect 함수를 다시 실행하라고 알려준다
- setFormIsValid, enteredEmail, enteredPassword가 마지막 컴포넌트 렌더링 주기에서 변경된 경우에만 실행하라고 알려준다
- 셋 중에 하나도 바뀐 게 없으면 이 이펙트 함수는 다시 실행되지 않는다
- setFormIsValid는 생략해도 되는데 이런 state 업데이트 함수는 기본적으로 리액트에 의해 절대 변경되지 않도록 보장되기 때문이다
- 일반적으로 특정 데이터, 어떤 state나 프롭이 변경될 때 로직을 다시 실행하기 위해 이 함수를 사용하기도 한다
- 사이드이펙트는 보통 http 리퀘스트 등인데 그 예로 키 입력을 듣고 입력된 데이터를 저장하는 것도 있고 그에 대한 응답으로 다른 액션을 실행하는 것도 있다
- 이메일, 비밀번호 필드의 키 입력에 대한 응답으로 해당 폼의 유효성을 확인하고 업데이트하는 것 또한 사이드이펙트이다
종속성으로 추가할 항목 및 추가하지 않을 항목
- effect 함수에서 사용하는 모든 것을 종속성으로 추가해야 한다. 즉, 거기에서 사용하는 모든 상태 변수와 함수를 포함한다
- 맞는 말이지만 예외가 있다
- 상태 업데이트 기능을 추가할 필요가 없다(setFormIsValid 사용) - 리액트는 해당 함수가 절대 변경되지 않도록 보장하므로 종속성으로 추가할 필요가 없다
- 내장 API 또는 함수를 추가할 필요가 없다. fetch()나 localStorage 같은 것들(브라우저에 내장된 함수 및 기능, 따라서 전역적으로 사용 가능) - 이런 브라우저 API/전역 가능은 리액트 구성 요소 렌더링 주기와 관련이 없으며 변경되지 않는다
- 변수나 함수를 추가할 필요가 없다(ex) 별도의 파일에 새 도우미 함수를 만드는 경우) - 이런 함수 또는 변수도 구성 요소 함수 내부에서 생성되지 않으므로 변경해도 구성 요소 함수 내부에서 생성되지 않으므로 변경해도 구성 요소에 영향을 주지 않는다(해당 변수가 변경되는 경우, 또는 그 반대의 경우에도 구성 요소는 재평가되지 않는다)
useEffect에서 Cleanup 함수 사용하기
- 일정량의 키 입력을 수집하거나 또는 키 입력 후 일정 시간 동안 일시 중지되는 것을 기다리는 것을 원한다
- 사용자가 타이핑을 멈추길 기다리고 그 이후에 유효한지 확인하길 원하는데 이것을 디바운싱 기술이라고 한다
- 사용자 입력을 디바운스(그룹화)하고 싶은 것
setTimeout(()=>{
setFormIsValid(
enteredEmail.includes("@") && enteredPassword.trim().length > 6
);
}, 500);
- 함수 실행하기 전 500밀리초 기다리기
- 타이머 저장하고 키가 입력되면 지운다. 타이머가 한 번에 하나만 실행되도록 하기 위해
- 사용자가 계속 입력하면 다른 모든 타이머는 계속 지워진다
- 따라서 완료되는 타이머는 하나뿐
- 첫 번째 인수에 반환값을 줄 수 있는데 함수 자체여야 한다 -> 클린업 함수라고도 하는데 클린업 프로세스로써 실행된다
- useEffect 함수가 실행될 때마다 클린업 함수가 실행된다
- 클린업 함수는 우리가 이펙트를 특정한 컴포넌트가 DOM에서 마운트 해제될 때마다 실행된다
- 클린업 함수는 모든 새로운 사이드이펙트 함수가 실행되기 전에 그리고 컴포넌트가 제거되기 전에 실행된다
- 첫 번째 사이드이펙트 함수가 실행되기 전에는 실행되지 않는다
useEffect(() => {
const identifier = setTimeout(() => {
setFormIsValid(
enteredEmail.includes("@") && enteredPassword.trim().length > 6
);
}, 500);
return () => {
console.log("CLEANUP");
clearTimeout(identifier);
};
}, [enteredEmail, enteredPassword]);
- return 값에 clearTimeout을 주면 클린업 함수가 실행될 때마다 클린업 함수가 실행되기 전에 설정된 타이머를 지운다
- 새로운 타이머를 설정하기 전에 마지막 타이머를 지우는 것
useEffect 요약
- 이펙트 함수는 컴포넌트가 처음 마운트 된 때를 포함해 모든 컴포넌트 렌더링 주기 후에 실행
- 의존성이 추가되면 컴포넌트가 재평가될 때마다 재실행된다
- 클린업 함수는 state 함수가 전체적으로 실행되기 전에 실행되지만 처음 실행되기 전에는 실행되지 않는다
useReducer 및 Reducers 일반 소개
- 강력한 state 관리가 필요한 경우 useState를 대체할 수 있다
- 다른 state를 기반으로 하는 state를 업데이트하면 그 경우에는 하나의 state로 병합하는 것도 좋다
useReducer() 훅 사용
- useReducer도 항상 두 개의 값이 있는 배열을 반환하기 때문에 배열 디스트럭처링 사용 가능
const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn);
- 반환되는 두 값중 하나는 최신 state 스냅샷이다 -> state 관리 메커니즘이기 때문에
- 또 하나는 업데이트할 수 있게 해주는 함수도 얻게 된다
- 리듀서 함수는 최신 state 스냅샷을 자동으로 가져오는 함수이고 이 함수는 디스패치된 액션을 가져온다
- 리액트는 새 액션이 디스패치될 때마다 이 리듀서 함수를 호출한다
- 리듀서 함수는 새로운 업데이트된 state를 반환한다
- 리듀서 함수는 컴포넌트 함수 바깥에 만드는데 리듀서 함수 내부에서는 컴포넌트 함수 내부에서 만들어진 어떤 데이터도 필요하지 않기 때문이다
- 리듀서 함수와 컴포넌트 함수 내부에서 정의된 그 어떤 것과도 상호작용할 필요가 없다
import React, { useState, useEffect, useReducer } from "react";
import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";
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 Login = (props) => {
const [enteredPassword, setEnteredPassword] = useState("");
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: "",
isValid: null,
});
useEffect(() => {
console.log("EFFECT RUNNING");
return () => {
console.log("EFFECT CLEANUP");
};
}, [enteredPassword]);
const emailChangeHandler = (event) => {
dispatchEmail({ type: "USER_INPUT", val: event.target.value });
setFormIsValid(
event.target.value.includes("@") && enteredPassword.trim().length > 6
);
};
const passwordChangeHandler = (event) => {
setEnteredPassword(event.target.value);
setFormIsValid(emailState.isValid && event.target.value.trim().length > 6);
};
const validateEmailHandler = () => {
dispatchEmail({ type: "INPUT_BLUR" });
};
const validatePasswordHandler = () => {
setPasswordIsValid(enteredPassword.trim().length > 6);
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, enteredPassword);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passwordIsValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={enteredPassword}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
useReducer & useEffect
- 객체디스트럭처링은 객체의 특정 속성 추출
import React, { useState, useEffect, useReducer } from "react";
import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";
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 };
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState("");
// const [emailIsValid, setEmailIsValid] = useState();
// const [enteredPassword, setEnteredPassword] = useState("");
// const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: "",
isValid: null,
});
const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
value: "",
isValid: null,
});
useEffect(() => {
console.log("EFFECT RUNNING");
return () => {
console.log("EFFECT CLEANUP");
};
}, []);
const { isValid: emailIsValid } = emailState;
const { isValid: passwordIsValid } = passwordState;
useEffect(() => {
const identifier = setTimeout(() => {
console.log("Checking form validity!");
setFormIsValid(emailIsValid && passwordIsValid);
}, 500);
return () => {
console.log("CLEANUP");
clearTimeout(identifier);
};
}, [emailIsValid, passwordIsValid]);
const emailChangeHandler = (event) => {
dispatchEmail({ type: "USER_INPUT", val: event.target.value });
// setFormIsValid(
// event.target.value.includes("@") && passwordState.isValid
// );
};
const passwordChangeHandler = (event) => {
dispatchPassword({ type: "USER_INPUT", val: event.target.value });
// setFormIsValid(emailState.isValid && event.target.value.trim().length > 6);
};
const validateEmailHandler = () => {
dispatchEmail({ type: "INPUT_BLUR" });
};
const validatePasswordHandler = () => {
dispatchPassword({ type: "INPUT_BLUR" });
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, passwordState.value);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passwordState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={passwordState.value}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
중첩 속성을 useEffect에 종속성으로 추가하기
- useEffect()에 객체 속성을 종속성으로 추가하기 위해 destructuring을 사용했는데 이건 매우 일반적인 패턴 및 접근 방식
- 핵심은 destructuring을 사용한다는 것이 아니라 특정 속성을 종속성으로 전달한다는 것이다
State 관리를 위한 useReducer 대 useState
- useReducer은 useState를 사용하면 너무 번거로운 경우 즉, 너무 많은 일을 처리해야 하는 경우에 사용한다
- 관련 state 스냅샷들이 서로 독립적이고 같이 업데이트가 잘 안된다면 useReducer을 사용한다
- useState는 주요 state 관리 도구이다
- 보통 useState로 시작하는데 대부분의 경우에는 그것만 있으면 된다
- 개별 state 및 데이터들을 다루기에 적합하고 간단한 state에 적합하다
- state 업데이트가 쉽고 몇 종류 안 되는 경우에 적합하다
- 만약 state로서의 객체가 있는 경우 또는 복잡한 state가 있다면 useReducer를 고려할 수 있다
- 일반적으로 useReducer는 리듀서 함수를 쓸 수 있기 때문에 더 강력하다
- 복잡한 state 업데이트 로직을 포함하는 리듀서 함수를 쓸 수 있다
- 또한 그 복잡할 수도 있는 로직을 컴포넌트 함수 body에서 별도의 리듀서 함수로 이동시킬 수도 있다
- 특히 useReducer를 고려해야 하는 경우는 연관된 state 조각들로 구성된 state 관련 데이터를 다루는 경우이다
- 예를 들어 폼 인풋 state가 있는 경우에는 useReducer가 도움이 될 수 있다
- 두 개의 서로 다른 값을 전환하기만 하는 단순한 state가 있는 경우라면 useReducer는 과도할 수 있다
- useReducer를 사용해 폼 state를 전체적으로 관리할 수도 있다
리액트 Context(Context API)소개
- 프롭을 통해 많은 컴포넌트를 거쳐 많은 데이터를 전달할 때 문제가 발생한다
- 데이터를 컴포넌트를 통해 다른 컴포넌트에 전달하기 위해 프롭 체인이 만들어지기 쉽다
리액트 컨텍스트 API 사용
- 리액트 컨텍스트는 리액트 내부적으로 state를 관리할 수 있도록 해주는 것
- 앱의 어떤 컴포넌트에서라도 직접 변경해 프롭체인을 구축하지 않아도 앱의 다른 컴포넌트에 직접 전달할 수 있게 해준다
- createContext는 기본 컨텍스트를 만든다
- 컨텍스트는 앱이나 빈 state의 컴포넌트일 뿐이기 때문에 그 state가 어떠해야 하는지는 우리가 정해야 하는데 대부분의 경우에는 객체를 사용한다
- createContext를 통해 얻는 것은 컴포넌트가 되거나 컴포넌트를 포함하는 객체가 될 것이다
- 앱에서 컨텍스트를 사용하려면 먼저 공급해야 하고 소비해야 한다
- 공급한다는 것은 JSX 코드로 감싸는 것을 의미한다
import React from "react";
const AuthContext = React.createContext({
isLoggedIn: false,
});
export default AuthContext;
useContext 훅으로 컨텍스트에 탭핑(tapping)하기
import React, { useContext } from "react";
import classes from "./Navigation.module.css";
import AuthContext from "../../store/auth-context";
const Navigation = (props) => {
const ctx = useContext(AuthContext);
return (
<nav className={classes.nav}>
<ul>
{ctx.isLoggedIn && (
<li>
<a href="/">Users</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<a href="/">Admin</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<button onClick={props.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
};
export default Navigation;
컨텍스트를 동적으로 만들기
- 컴포넌트뿐만 아니라 함수에도 데이터를 전달하도록 동적 컨텍스트를 설정할 수 있다
- 예를 들어 내비게이션처럼 매우 특정적인 일을 하는 컴포넌트에서 항상 사용자를 로그아웃시키는 버튼 같은 경우에 컴포넌트로 전달하는 경우에만 컨텍스트를 사용하는 게 좋다
- 컨텍스트를 사용하면 더 간결한 코드를 작성할 수 있고 앱 전체 state 관리가 조금 더 쉬워진다
사용자 정의 컨텍스트 제공자 구성요소 빌드 및 사용
auth-context.js
import React, { useState, useEffect } from "react";
const AuthContext = React.createContext({
isLoggedIn: false,
onLogout: () => {},
onLogin: (email, password) => {},
});
export const AuthContextProvider = (props) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
const storedUserLoggedInInformation = localStorage.getItem("isLoggedIn");
if (storedUserLoggedInInformation === "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;
리액트 컨텍스트 제한
- 앱 전체 또는 컴포넌트 전체 state에는 컨텍스트가 적합할 수 있다
- 하지만 컴포넌트 구성을 대체할 수는 없다
- state가 1초에도 여러 번 바뀌는 경우라면 리액트 컨텍스트는 이에 최적화되어 있지 않다
- state가 자주 변경되는 경우에 더 적합한 도구는 리덕스
"Hooks의 규칙" 배우기
- 리액트 훅은 단순히 use로 시작하는 모든 함수이다
- 리액트 훅은 리액트 컴포넌트 함수 또는 사용자 정의 훅에서만 호출해야 한다
- 리액트 훅은 리액트 컴포넌트 함수 또는 사용자 정의 훅 함수의 최상위 수준에서만 호출해야 한다
- 중첩 함수나 block문, if문에서 호출하면 안 된다
- 항상 참조하는 모든 항목을 의존성으로 useEffect 내부에 추가해야 한다
입력 컴포넌트 리팩토링
Input.js
import React from "react";
import classes from "./Input.module.css";
const Input = (props) => {
return (
<div
className={`${classes.control} ${
props.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor={props.id}>{props.label}</label>
<input
type={props.type}
id={props.id}
value={props.value}
onChange={props.onChange}
onBlur={props.onBlur}
/>
</div>
);
};
export default Input;
Forward Refs에 대해 알아보기
- 컴포넌트 내부에서 함수를 호출하는 방식은 일반적인 패턴이 아니기 때문에 자주 할 필요도 없고 자주 해서도 안 된다.
Input.js
import React, { useRef, useImperativeHandle } from "react";
import classes from "./Input.module.css";
const Input = React.forwardRef((props, ref) => {
const inputRef = useRef();
const activate = () => {
inputRef.current.focus();
};
useImperativeHandle(ref, () => {
return {
focus: activate,
};
});
return (
<div
className={`${classes.control} ${
props.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor={props.id}>{props.label}</label>
<input
ref={inputRef}
type={props.type}
id={props.id}
value={props.value}
onChange={props.onChange}
onBlur={props.onBlur}
/>
</div>
);
});
export default Input;
Login.js
import React, {
useState,
useEffect,
useReducer,
useContext,
useRef,
} from "react";
import Card from "../UI/Card/Card";
import Button from "../UI/Button/Button";
import AuthContext from "../../store/auth-context";
import Input from "../UI/Input/Input";
import classes from "./Login.module.css";
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 };
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState('');
// const [emailIsValid, setEmailIsValid] = useState();
// const [enteredPassword, setEnteredPassword] = useState('');
// const [passwordIsValid, setPasswordIsValid] = useState();
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();
useEffect(() => {
console.log("EFFECT RUNNING");
return () => {
console.log("EFFECT CLEANUP");
};
}, []);
const { isValid: emailIsValid } = emailState;
const { isValid: passwordIsValid } = passwordState;
useEffect(() => {
const identifier = setTimeout(() => {
console.log("Checking form validity!");
setFormIsValid(emailIsValid && passwordIsValid);
}, 500);
return () => {
console.log("CLEANUP");
clearTimeout(identifier);
};
}, [emailIsValid, passwordIsValid]);
const emailChangeHandler = (event) => {
dispatchEmail({ type: "USER_INPUT", val: event.target.value });
// setFormIsValid(
// event.target.value.includes('@') && passwordState.isValid
// );
};
const passwordChangeHandler = (event) => {
dispatchPassword({ type: "USER_INPUT", val: event.target.value });
// setFormIsValid(emailState.isValid && event.target.value.trim().length > 6);
};
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 className={classes.login}>
<form onSubmit={submitHandler}>
<Input
ref={emailInputRef}
id="email"
label="E-Mail"
type="email"
isValid={emailIsValid}
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
<Input
ref={passwordInputRef}
id="password"
label="Password"
type="password"
isValid={passwordIsValid}
value={passwordState.value}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
<div className={classes.actions}>
<Button type="submit" className={classes.btn}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
728x90
'Frontend > React' 카테고리의 다른 글
[Udemy React 완벽 가이드] Section 12 (1) | 2024.01.04 |
---|---|
[Udemy React 완벽 가이드] Section 11 (1) | 2024.01.04 |
[Udemy React 완벽 가이드] Section 8 (2) | 2024.01.04 |
[Udemy React 완벽 가이드] Section 7 (1) | 2024.01.04 |
[Udemy React 완벽 가이드] Section 6 (2) | 2024.01.04 |