처음에 훅을
export const useQueryParam = (defaultParams: I_QueryParam) => {
const [searchParams, setSearchParams] = useSearchParams();
// 디폴트 쿼리 파라미터 설정
useEffect(() => {
// 기본값을 기준으로 현재 searchParams에 없는 값을 설정
let updated = false;
for (const [key, value] of Object.entries(defaultParams)) {
if (!searchParams.has(key)) {
searchParams.set(key, value);
updated = true;
}
}
// 변경 사항이 있을 때만 URL 업데이트
if (updated) {
setSearchParams(searchParams);
}
}, [searchParams, setSearchParams]);
컴포넌트에서 이렇게 했는데 company api가 2번 호출 됐다.
const Company = () => {
useEffect(() => {
const initKeyword = getQueryParam("keyword");
initKeyword && setKeyword(initKeyword);
if (getQueryParam("status")) {
if (getQueryParam("tab") === COMPANY_MENU.company) {
deleteQueryParam("status"); // company탭은 status 없음
} else {
form.setFieldValue("status", getQueryParam("status"));
}
}
if (getQueryParam("tab") === COMPANY_MENU.company) {
fetchCompanies();
} else if (getQueryParam("tab") === COMPANY_MENU.requestReview) {
fetchRequestReview();
}
}, [searchParams]);
훅은 이렇게 고쳐야헸다 (dependency 빈 배열)
useEffect에서 searchParams와 setSearchParams를 의존성 배열에 포함하는 것은 일반적으로 비효율적일 수 있습니다. 특히, searchParams는 객체이므로 참조가 변경될 때마다 useEffect가 실행됩니다. 이를 방지하기 위해 의존성을 최적화해야 합니다.
문제점:
- searchParams 객체 참조 변경
searchParams 객체가 변경될 때마다 useEffect가 실행됩니다. 이로 인해 불필요한 실행이 발생할 수 있습니다. - setSearchParams는 stable 함수
setSearchParams는 React Router에서 제공하는 함수로 보통 안정적입니다. 의존성 배열에서 제거해도 무방합니다.
컴포넌트에서 useeffect도 searchParams를 디펜던시로 사용하면 안 된다.
컴포넌트에 진입할 때 fetchCompanies가 두 번 호출되는 문제는 useEffect의 의존성 배열과 searchParams의 변경 방식에서 발생할 가능성이 높습니다.
문제 원인 분석
- setSearchParams가 useEffect를 트리거
useQueryParam에서 setSearchParams를 호출하면 searchParams 객체가 갱신됩니다. 이로 인해 의존성 배열에 포함된 searchParams가 변경되고, 이를 감지한 두 번째 useEffect가 실행됩니다. - searchParams의 참조 변경
URLSearchParams 객체는 변경될 때마다 새 참조값을 가지므로, 불필요한 렌더링 및 useEffect 실행을 초래할 수 있습니다.
디펜던시에 [getQueryParam("tab")] 처럼 특정 쿼리가 변할때만 호출하게 하라는 방법도 추천해줬지만
나는 쿼리가 많고 queryparam이 바뀔때만 api를 호출하고 싶은데 어떻게 해결하냐
여기서 힌트를 얻었다
https://dev-thinking.tistory.com/23
[개발일지02/🏔️오름마켓 React] useSearchParams로 필터 Query String 구현하기
🙌 리팩토링을 통해 프론트엔드에서 처리되던 정렬 및 필터 기능을 API와 연결하는 작업을 진행했다. 렌더링 된 버튼의 값과 실제 API 요청을 위해 전달해 줘야 했던 Query String의 값이 달라 두 값
dev-thinking.tistory.com
useLocation을 활용하면 URL의 변경을 감지하여 useEffect가 실행되도록 제어할 수 있습니다. 이를 통해 searchParams의 변경으로 인해 발생하는 불필요한 재실행을 줄일 수 있습니다.
위 방법들로 초기 URL 상태가 빈 문자열일 때의 실행을 방지하거나, 중복 호출을 막을 수 있습니다:
- if (!location.search) 조건 추가로 빈 문자열 방지.
- hasFetched 상태로 중복 호출 방지.
- **useRef**를 사용해 초기 렌더링을 한 번만 실행하도록 제어.
최종 코드
const location = useLocation();
useEffect(() => {
if (!location.search) return;
const initKeyword = getQueryParam("keyword");
initKeyword && setKeyword(initKeyword);
if (getQueryParam("status")) {
if (getQueryParam("tab") === COMPANY_MENU.company) {
deleteQueryParam("status"); // company탭은 status 없음
} else {
form.setFieldValue("status", getQueryParam("status"));
}
}
if (getQueryParam("tab") === COMPANY_MENU.company) {
fetchCompanies();
} else if (getQueryParam("tab") === COMPANY_MENU.requestReview) {
fetchRequestReview();
}
}, [location.search]); // URL이 변경될 때만 호출
export const useQueryParam = (defaultParams: I_QueryParam) => {
const [searchParams, setSearchParams] = useSearchParams();
// 디폴트 쿼리 파라미터 설정
useEffect(() => {
// 기본값을 기준으로 현재 searchParams에 없는 값을 설정
let updated = false;
for (const [key, value] of Object.entries(defaultParams)) {
if (!searchParams.has(key)) {
searchParams.set(key, value);
updated = true;
}
}
// 변경 사항이 있을 때만 URL 업데이트
if (updated) {
setSearchParams(searchParams);
}
}, []);
2-1. API 호출 중복 방지 상태 추가
API 호출 중인지 상태를 관리하여 중복 호
const [hasFetched, setHasFetched] = useState(false);
useEffect(() => {
if (!location.search || hasFetched) return;
console.log(location.search);
const initKeyword = getQueryParam("keyword");
if (initKeyword) {
setKeyword(initKeyword);
}
if (getQueryParam("status")) {
if (getQueryParam("tab") === COMPANY_MENU.company) {
deleteQueryParam("status"); // company탭은 status 없음
} else {
form.setFieldValue("status", getQueryParam("status"));
}
}
if (getQueryParam("tab") === COMPANY_MENU.company) {
fetchCompanies();
} else if (getQueryParam("tab") === COMPANY_MENU.requestReview) {
fetchRequestReview();
}
setHasFetched(true);
}, [location.search]);
3-1방법
초기 렌더링 시 딱 한 번만 실행
초기 렌더링에서 한 번 실행되도록 useRef를 사용할 수도 있습니다:
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current && !location.search) {
isFirstRender.current = false; // 첫 렌더링 시 실행 방지
return;
}
console.log(location.search);
const initKeyword = getQueryParam("keyword");
if (initKeyword) {
setKeyword(initKeyword);
}
if (getQueryParam("status")) {
if (getQueryParam("tab") === COMPANY_MENU.company) {
deleteQueryParam("status"); // company탭은 status 없음
} else {
form.setFieldValue("status", getQueryParam("status"));
}
}
if (getQueryParam("tab") === COMPANY_MENU.company) {
fetchCompanies();
} else if (getQueryParam("tab") === COMPANY_MENU.requestReview) {
fetchRequestReview();
}
}, [location.search]);
추가사항
URL 전체 변경을 감지하고 싶다면?
location.pathname과 location.search를 함께 사용
useEffect(() => {
fetchAPI(`${location.pathname}${location.search}`);
}, [location.pathname, location.search]);
Debounce를 통한 호출 최적화
URL 변경이 매우 빈번할 경우(예: 사용자가 검색어를 입력 중일 때), 호출을 최적화하기 위해 debounce를 사용
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { debounce } from "lodash";
const MyComponent = () => {
const location = useLocation();
useEffect(() => {
const debouncedFetch = debounce(() => fetchAPI(location.search), 300);
debouncedFetch();
return debouncedFetch.cancel; // 컴포넌트 언마운트 시 debounce 해제
}, [location.search]);
const fetchAPI = async (queryString) => {
// API 호출 로직
};
return <div>My Component</div>;
};
'문제해결' 카테고리의 다른 글
npm install 안 될 때 해결법 (0) | 2024.08.21 |
---|---|
[AWS] Elastic Ip 삭제 (0) | 2024.02.28 |
route53 도메인에 접속할 때 ERR_CONNECTION_REFUSED (0) | 2024.02.07 |
ubuntu Swap Space 추가하기 (0) | 2024.01.11 |
[ERROR] Your password does not satisfy the current policy requirements 해결 (0) | 2024.01.09 |