1️⃣ 컴포넌트를 이동시킬 장소가 필요
<!-- index.html -->
<body>
<div id="backdrop-root"></div>
<div id="overlay-root"></div>
<div id="root"></div>
</body>
2️⃣ 컴포넌트에게 그 곳에 포털을 가져야 한다고 알림
- react-dom 라이브러리의 createPotal(렌더링이 되어야하는 리액트 노드, 포인터)
- 첫번째 인수: 렌더링이 되어야하는 리액트 노드를 jsx로 작성.
- 두번째 인수: 포인터, 이 요소가 렌더링 되어야 하는 실제 DOM의 컨테이너를 가리키는 포인터
// ErrorModal.js
import ReactDOM from 'react-dom';
...
const Backdrop = props => {
return <div className={classes.backdrop} onClick={props.onConfirm} />;
}
const ModalOverlay = props => {
return (
<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>
);
}
...
const ErrorModal = (props) => {
return(
<React.Fragment>
{ReactDOM.createPortal(
<Backdrop onConfirm={props.onConfirm} />,
document.getElementById('backdrop-root')
)}
{ReactDOM.createPortal(
<ModalOverlay
title={props.title}
message={props.message}
onConfirm={props.onConfirm}
/>,
document.getElementById('overlay-root')
)}
</React.Fragment>
);
}
useRef(초기값)
: 객체를 반환하는데, 안에 설정한 DOM노드로 값을 갖는 current프롭이 항상 있음.1️⃣ import
import {useRef} from 'react';
2️⃣ ref로 받아올 변수 선언
const nameInputRef = useRef();
3️⃣ ref프롭으로 dom 요소 연결
<input ref={nameInputRef} />
4️⃣ current프롭 사용
console.log(nameInputRef.current.value);
🔥 주의사항 🔥
: 사용자 지정 컴포넌트에서 ref={}프롭을 사용하기 위해서는 해당 컴포넌트로 가서 컴포넌트함수를 React.forwardRef()로 감싸야 함!!, 사용할 요소에 ref={ref}를 추가하면, 이제 ref프롭을 사용할 수 있음.
👾 Input.js - 자식 컴포넌트
const Input = (props) => {
const inputRef = useRef();
const activate = () => {
// 제대로 focus되지 않음.
inputRef.current.focus();
};
return (
<div
className={`${classes.control} ${
props.isValid === false ? classes.invalid : ''
}`}
>
<label htmlFor={props.id}>{props.label}</label>
<input
// ref 등록
ref={inputRef}
type={props.type}
id={props.id}
value={props.value}
onChange={props.onChange}
onBlur={props.onBlur}
/>
</div>
);
}
👾 Login.js - 부모 컴포넌트
...
const submitHandler = (event) => {
event.preventDefault();
if (formIsValid) {
authCtx.onLogin(emailState.value, passwordState.value);
} else if (!emailIsValid) {
// Input컴포넌트의 activate()호출 => error
emailInputRef.current.activate();
} else {
// Input컴포넌트의 activate()호출 => error
passwordInputRef.current.activate();
}
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<Input
// ref 등록
ref={emailInputRef}
id="email"
label="E-Mail"
type="email"
isValid={emailIsValid}
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
<Input
// ref 등록
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>
);
👉🏻 Login.js의 Input컴포넌트에서 ref={passwordInputRef}
와 같이 ref프롭으로 넘겨준 값을 Input.js의 <input ref={inputRef} .../>
를 등록하여도 🔥 함수 컴포넌트(Input컴포넌트)는 ref를 받을 수 없기 때문에 오류가 발생함!
1️⃣ useImperativeHandle()훅 사용
useImperativeHandle( ref, ()=>{ return ({}); } )
React.forwardRef((props, ref)=>{});
👾 Input.js - 자식 컴포넌트
import React, { useRef, useImperativeHandle } from 'react';
const Input = React.forwardRef((props, ref) => {
const inputRef = useRef();
const activate = () => {
inputRef.current.focus();
};
useImperativeHandle(ref, () => {
return {
// focus는 위 activate함수 식별자를 참조함!!
// focus는 외부(부모컴포넌트 사이)에서 사용가능!
focus: activate,
};
});
return (
<div
className={`${classes.control} ${
props.isValid === false ? classes.invalid : ''
}`}
>
<label htmlFor={props.id}>{props.label}</label>
<input
// ref 등록
ref={inputRef}
type={props.type}
id={props.id}
value={props.value}
onChange={props.onChange}
onBlur={props.onBlur}
/>
</div>
);
});
👾 Login.js - 부모 컴포넌트
...
const submitHandler = (event) => {
event.preventDefault();
if (formIsValid) {
authCtx.onLogin(emailState.value, passwordState.value);
} else if (!emailIsValid) {
// 자식 컴포넌트(Input)의 focus함수를 실행
emailInputRef.current.focus();
} else {
passwordInputRef.current.focus();
}
};
👉🏻 Input컴포넌트의 useImperativeHandle()에 추가한 focus함수는 외부(Login.js)에서 사용할 수 있기 때문에 active()함수가 실행되어 유효하지 않는 input에 focus가 적용된다!!
[참고] Udemy - React 완벽 가이드 with Redux, Next.js, TypeScript