반응형
이번에 보여줄 훅은 Input 컴포넌트와 명령형으로 상호 작용할 수 있게 해준다
즉, 예를 들어 어떤 state를 전달해서 그 컴포넌트에서 무언가를 변경하는 방식이 아니라
컴포넌트 내부에서 함수를 호출하는 방식으로 이용한다.
또 자주 할 필요도 없겠지만 자주 해서도 안 된다
이제는 Login 버튼을 활성화해서 유효하지 않은 값을 넣은 input 창에 효과를 주도록 하겠다.
useRef에 대해서 기억하는가?값이 유효하지 않을 때, 이 개념을 사용할 것이다
ref prop은 Input 같은 모든 내장 HTML 컴포넌트에서 지원된다
아래와 같이 내장 객체를 이용, useEffect를 이용해서 password에 focus를 주었지만 우리가 원하는 것은 이게 아니다
import React, { useRef, useEffect } from 'react';
import classes from './input.module.css';
const Input = (props) => {
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
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;
목표는 activate 함수를 Input 내부가 아니라 외부에서 호출하려는 것이다
> 흔치 않은 경우다
Login.js 로 가서
const emailInputRef = useRef();
const passwordInputRef = useRef();
const submitHandler = (event) => {
event.preventDefault();
if (formIsValid) {
authCtx.onLogin(emailState.value, passwordState.value);
} else if (!emailIsValid) {
emailInputRef.current.focus();
} else {
passwordInputRef.current.focus();
}
};
하고 진행하면 될 줄 알았으나 안 된다!
그럴 때 useImperativeHandle 훅을 사용하면 컴포넌트나 컴포넌트 내부에서 오는 기능들을 명령적으로 사용할 수 있게 해준다
이 훅은 자주 사용하는 훅이 아니다.
useImperativeHandle 및 forwardRef를 사용하면
리액트 컴포넌트에서 온 기능을 노출하여 부모 컴포넌트에 연결한 다음, 부모 컴포넌트 안에서 참조를 통해 그 컴포넌트를 사용하고 기능을 Trigger 할 수 있다.
그러나 이 기능은 가능하면 반드시 피하는 것이 좋지만 특히 이와 같이 포커스나
스크롤링같은 다른 사용 사례에서는 매우 유용할 수 있다
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 classes from './Login.module.css';
import Button from '../UI/Button/Button';
import AuthContext from '../store/auth-context';
import Input from '../UI/Input/Input';
const Login = () => {
const authCtx = useContext(AuthContext);
// const [enteredEmail, setEnteredEmail] = useState('');
// const [emailIsValid, setEmailIsValid] = useState();
// const [enteredPassword, setEnteredPassword] = useState('');
// const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const emailReducer = (state, action) => {
if (action.type === 'USER_INPUT') {
return { value: action.val, isValid: action.val.includes('@') }
} else if (action.type === 'USER_BLUR') {
return { value: state.value, isValid: state.value.includes('@') }
}
return { value: '', isValid: false };
}
const passwordReducer = (state, action) => {
if (action.type === 'PWD_INPUT') {
return { value: action.val, isValid: action.val.trim().length > 6 };
} else if (action.type === 'PWD_BLUR') {
return { value: state.value, isValid: state.value.trim().length > 6 };
}
return { value: state.value, isValid: false };
}
const [emailState, dispatchEmail] = useReducer(emailReducer, { value: '', isValid: null });
const [passwordState, dispatchPassword] = useReducer(passwordReducer, { value: '', isValid: null });
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) => {
// setEnteredEmail(event.target.value);
dispatchEmail({ type: 'USER_INPUT', val: event.target.value })
setFormIsValid(
emailState.isValid && passwordState.isValid
);
};
const passwordChangeHandler = (event) => {
dispatchPassword({ type: 'PWD_INPUT', val: event.target.value })
// setEnteredPassword(event.target.value);
setFormIsValid(
emailState.isValid && event.target.value.trim().length > 6
);
};
const validateEmailHandler = () => {
// setEmailIsValid(emailState.isValid);
dispatchEmail({ type: 'USER_BLUR' });
};
const validatePasswordHandler = () => {
// setPasswordIsValid(enteredPassword.trim().length > 6);
dispatchPassword({ type: 'PWD_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;
반응형
'온라인 강의 > React 완벽 가이드 [Udemy]' 카테고리의 다른 글
140. 모듈 소개 - 섹션 11: 연습 프로젝트: 음식 주문 앱을 만들어보자(Building a Food Order App) (0) | 2023.06.14 |
---|---|
139. 모듈 리소스 (0) | 2023.06.14 |
137. 입력 컴포넌트 리팩토링 (0) | 2023.06.14 |
136. "Hooks의 규칙" 배우기 (0) | 2023.06.14 |
135. 리액트 컨텍스트 제한 (0) | 2023.06.14 |