
function MyUserInfo() {
const [userName, setUserName] = useState("Bob");
return (
<div>
<input
value={userName}
onChange={(evt) => setUserName(evt.target.value)}
/>
</div>
);
}
input에 현재 값을 표출하고 바꿀수도 있다.
function MyUserInfo() {
const [userName, setUserName] = useState("Bob");
const [loginMessage, setLoginMessage] = useState("");
useEffect(() => {
let loggedInTime = 0;
const interval = setInterval(() => {
loggedInTime++;
setLoginMessage(
`${userName} has been logged in for ${loggedInTime} seconds`
);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<div>{loginMessage}</div>
<input
value={userName}
onChange={(evt) => setUserName(evt.target.value)}
/>
</div>
);
}
로그인 메세지 안에 이름과 loggedInTime이 기록된다.
잘되는것 같지만 onChange에서 userName을 바꾸려고 하지만 변경이 안된다.
그렇다면 의존성 배열에 userName을 추가하는 방법이 있을거다.
useEffect(() => {
let loggedInTime = 0;
const interval = setInterval(() => {
loggedInTime++;
setLoginMessage(
`${userName} has been logged in for ${loggedInTime} seconds`
);
}, 1000);
return () => clearInterval(interval);
}, [userName]);
이렇게 되면 userName을 바꿀 때마다 새로운 함수 생성이 되고 이름이 잘 바뀌지만 동시에 loggedInTime도 계속해서 0으로 초기화되어버린다.
useEffectEvent가 생기기 전에는 useRef로 해결했었다.
const nameRef = useRef(userName);
nameRef.current = userName;
useEffect(() => {
let loggedInTime = 0;
const interval = setInterval(() => {
loggedInTime++;
setLoginMessage(
`${nameRef.current} has been logged in for ${loggedInTime} seconds`
);
}, 1000);
return () => clearInterval(interval);
}, []);
useRef를 이용해 userName의 current를 참조하고 있고 컴포넌트 렌더링 때마다 할당하지만 state처럼 참조하는게 아니라 괜찮다.
이로써 이름을 변경해도 타이머 loggedInTime이 유지되는 기능을 구현했다.
하지만 새로운 해결법이 나왔다.
const getName = useEffectEvent(() => userName);
useEffect(() => {
let loggedInTime = 0;
const interval = setInterval(() => {
loggedInTime++;
setLoginMessage(
`${getName()} has been logged in for ${loggedInTime} seconds`
);
}, 1000);
return () => clearInterval(interval);
}, []);
useEffectEvent 훅으로 userName의 current 값을 반환할 수 있다.
useEffect 안에서만 호출 가능하고 절대 stale 하지 않다. 그리고 코드도 훨씬 clear 하다.
그리고 useEffect 훅에 대해 더욱 타이머로 생각할수 있게 된다.
원래는 타이머 설정 이라는 로직과 “현재 userName으로 메시지 만들기” 라는 로직이 한 덩어리로 섞여 있었음.
const onTick = useEffectEvent((tick: number) =>
setLoginMessage(`${userName} has been logged in for ${tick} seconds`)
);
useEffect(() => {
let ticks = 0;
const interval = setInterval(() => onTick(++ticks), 1000);
return () => clearInterval(interval);
}, []);
이렇게 타이머 외에 다른 state는 useEffectEvent에 옮김으로써 useEffect 로직이 훨씬 깔끔해졌다.
참고 글에 마지막 문장이 아래와 같다.
Bad dependency arrays that depend on the wrong state can cause stale closure issues, invalid resets, or even infinite loops. And useEffectEvent allows us to remove state from the dependency array. And that helps us write better useEffects.
잘못된 state를 참조하는 나쁜 의존성 배열은 stale 클로저 이슈, 유효하지 않은 리셋, 심지어 무한루프까지 생길수 있다. useEffectEvent는 의존성 배열에서 state 제거를 도와주고 좋은 useEffect 활용을 돕는다.