✅한 줄 요약 : useEffect 는 React 컴포넌트 렌더링이 완료된 직후에 비동기로 호출된다.
0. 버그 현황
- 로그아웃 버튼을 누르면 / 페이지로 리다이렉트 기능을 만들려고 했다.
- 로그아웃 버튼을 누르면 토큰 값이 없어져서 / 페이지로 이동해야하는데 Notfound 페이지로 뜸
- 아래 코드를 보면 중간에 useEffect 로 token 값이 없으면 / 페이지로 이동하게 해놨다.
function Todo() {
const [showInp, setShowInp] = useState<boolean>(false);
const [todoList, setTodoList] = useState<TodoItem[]>([]);
const userContext = useContext(UserContext);
const navigate = useNavigate();
if (!userContext) {
throw new Error("UserContext is null");
}
const { token, setToken } = userContext;
useEffect(() => {
if (!token) {
navigate("/");
}
}, [token]);
const handleRefresh = () => {
location.reload();
};
const handleLogout = () => {
localStorage.removeItem("access_token");
setToken(null);
};
return (
<div className="bg-main_skyblue flex flex-col justify-center items-center h-screen">
<aside className="w-98 text-right mr-5 mb-5">
<FontAwesomeIcon
icon={faHouse}
className="cursor-pointer mr-3 "
style={{ color: "#50b4fc" }}
size="xl"
onClick={handleRefresh}
/>
<img
src={Logout}
alt="로그아웃"
className="w-6 cursor-pointer inline"
onClick={handleLogout}
/>
</aside>
{/*중략*/}
</div>
);
}
export default Todo;
- 하지만 어쩐일인지 NotFound 페이지로 이동을 한다.
export default function Router() {
const token = useContext(UserContext)?.token;
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Splash />} />
{token ? (
<>
<Route path="/todo" element={<Todo />} />
<Route path="/todo/category" element={<Category />} />
</>
) : (
<>
<Route path="/signin" element={<SignIn />} />
<Route path="/signup" element={<SignUp />} />
</>
)}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
function App() {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
const access_token = localStorage.getItem("access_token");
if (access_token) {
setToken(access_token);
}
}, []);
return (
<div className="App">
<UserContext.Provider value={{ token, setToken }}>
<Router />
</UserContext.Provider>
</div>
);
}
export default App;
1. 버그 해결 과정
- 로그아웃 버튼을 누르고 handleLogout 함수가 실행이 되면, 토큰이 제거되고, setToken에 의해 null 이된다.
const handleLogout = () => {
localStorage.removeItem("access_token");
setToken(null);
};
- 즉, Context Api 에 있는 token 이 null 이 되어버렸다. setToken(null) 이 되었는데 Todo 컴포넌트 내에 useEffect 가 먼저 동작하는게 아니고, Router 코드가 먼저 동작을 했다. 왜그럴까??
useEffect(() => {
if (!token) {
navigate("/");
}
}, [token]);
어쨌든, Context API 에 있는 token 상태가 null 이 되었으면 마찬가지로 token 이 null 이니 / 페이지로 이동해야하는 것이 아닌가? 왜 Router 페이지부터 적용이 되는걸까?
useEffect 와 라우팅 처리에 대해서 알아보자
🔸 useEffect
useEffect 함수는 React 컴포넌트 렌더링이 완료된 직후에 비동기로 호출된다.
따라서, Todo 컴포넌트에서 useEffect 훅을 사용하여 토큰의 유무를 확인하고 있지만, 로그아웃 버튼을 눌러서 토큰이 null 이 된 직후에 바로 이 'useEffect'가 호출되는 건 아니다. 즉, Todo 컴포넌트의 useEffect 는 다음 렌더링 사이클에서야 비로소 호출된다.
🔸Router 컴포넌트
Context API 에 의해 state가 갱신되면 하위 컴포넌트들이 전부 렌더링이 된다.
setToken에 의해 토큰이 null 이 되어버리면 'Router' 컴포넌트가 리렌더링되어 적절한 라우트를 활성화 하게 된다.
기존에 token 이 있을 때는 /todo 라우터가 활성화되었지만 setToken(null) 로 인해 토큰이 없어짐 -> 재 렌더링 -> 토큰이 없는데 todo 페이지에 있다면 /notfound 라우트 활성화하게 됨
결론적으로, 로그아웃 버튼을 눌렀을 때에 먼저 라우터가 업데이트 되고(Context API 의 SetToken 이 null 이 되니까 모든 하위값 렌더링 때문에) 그 후에 'Todo' 컴포넌트의 'useEffect' 가 호출이 된다. 이때문에 토큰이 null 이 되면 /페이지로 리디렉션이 되는게 아니라 'Router' 컴포넌트의 라우팅 설정에 따라서 처리된다.
2. 해결책
- useEffect 를 사용하는 것이 아니라 로그아웃 버튼을 눌렀을 때, 바로 '/' 로 이동하는 로직을 추가해준다.
1) 첫번째 해결책
const handleLogout = () => {
localStorage.removeItem("access_token");
setToken(null);
navigate("/");
};
- 이코드는 JavaScript 의 단일 스레드의 특징에 의해 동기적으로 실행이된다. 따라서 코드가 순차적으로 실행이 된다.
- 하지만 setToken(null) , 즉 React 의 상태 설정 함수가 비동기적으로 동작하기 때문에 상태의 변경이 즉시 일어나지 않을 수 있다. 변경사항은 예약되어있고, 실제 반영은 나중에 React 에 의해 배치 처리된다.
- 결국, 이 코드는 동기적으로 실행되지만 'setToken' 함수에 의한 상태변경은 비동기적으로 처리된다. navigate 함수는 동기적으로 호출되고 즉시 페이지 이동을 수행하기에 'setToken' 에 의한 상태 변경이 완료되기 전에 페이지 이동이 일어날 수 있다.
- 만약, 상태 업데이트 후에 특정 작업을 수행하려면 'useEffect' 훅으로 상태 변경 후에 작업을 하도록 할 수 있다.
'useEffect' 는 상태가 실제로 업데이트 된 후에 호출된다. 하지만, 우리코드에서는 리액트 라우터가 렌더링 된 이후에 useEffect가 실행되기 때문에 useEffect로 처리할 수 없다.
2) 두번째 해결책
const handleLogout = () => {
localStorage.removeItem("access_token");
setToken(() => {
navigate("/");
return null;
});
};
- 이 코드도 마찬가지로 'setToken' 내부에서 'navigate'를 호출하는 건 상태 변경이 완료되었다는 것을 보장할 수 없다.
🤔 setToken(null) 이 확정이 되고 나서 / 페이지로 이동하게끔. 동.기.적 으로 확실하게 보장하는 로직의 해결책은 없을까...? 우선 1차적인 해결방법으로 기능 구현을 하고 동기적으로 실행되게끔 하는 기능은 추후에 살펴보도록 해야겠다
'프로젝트 이모저모' 카테고리의 다른 글
[투두리스트 버그] 전역 상태 값이 바로 변경되어 렌더링 되지 않음 (0) | 2023.08.01 |
---|---|
[투두리스트 페어프로그래밍] recoil 새로 고침 시 전역 데이터 초기화 (0) | 2023.07.24 |
[투두리스트 페어프로그래밍 버그] Context Api - 토큰에 따른 페이지 접근 제한 (0) | 2023.07.10 |
[투두리스트 페어프로그래밍] 전역상태관리 VS get API 재호출 (0) | 2023.07.07 |
[투두리스트 페어프로그래밍] 지역변수, useRef, useState 차이 (0) | 2023.07.05 |